Found while sweeping for other copies of #374. This is the same genus but at the _run_git chokepoint, which makes it strictly more severe: the exception escapes untyped, defeating the very taxonomy that function exists to provide.
The defect
verify._run_git decodes git's stdout with text=True (locale codec, errors='strict') and translates only two exception classes:
except subprocess.TimeoutExpired as exc:
raise GitError(...)
except OSError as exc:
raise GitSpawnError(...)
Its own docstring states the contract:
Two failures are raised by subprocess.run before any return code exists — a timeout (#156) and a spawn-level OSError (#343) — so left uncaught either would bypass every except GitError guard and crash the run.
A UnicodeDecodeError is a third such failure, raised inside subprocess.run before any return code exists, and it is not in the taxonomy — so it does exactly what the docstring says must not happen.
Why ordinary git commands are safe (and which are not)
Git quotes non-ASCII paths by default (core.quotePath), so git status --porcelain returns pure ASCII (?? "weird-\377-name.txt") and decodes fine. -z disables that quoting and emits raw path bytes — which is exactly what _git_raw exists for:
Like _git but returns stdout verbatim (no strip, no stderr merge) — for NUL-delimited (-z) output...
Reproduction
A repo containing one file whose name is not valid UTF-8 (POSIX filenames are arbitrary bytes):
verify._git(repo, "status", "--porcelain") -> rc=0 ok out='?? "weird-\\377-name.txt"'
verify._git_raw(repo, "status", "--porcelain", "-z")
-> RAISED UnicodeDecodeError -> 'utf-8' codec can't decode byte 0xff in position 9
is GitError? False
Affected call sites
All three -z invocations:
| line |
call |
reached from |
verify.py:779 |
status --porcelain -z -uall in dirty_paths |
clean_incoming_collisions (merge pre-flight) |
verify.py:804 |
diff --name-only -z in branch_incoming_paths |
clean_incoming_collisions |
verify.py:2013 |
ls-files -z in commit_paths |
decisions.py:161, cli.py:1859 |
clean_incoming_collisions raises GitError deliberately when it finds stray paths, so its callers guard on GitError — and this fault sails straight past that guard.
Suggested fix
Add the decode fault to the existing taxonomy rather than trying to make non-UTF-8 paths work:
except UnicodeDecodeError as exc:
raise GitError(f"git {cmd[3]} returned undecodable output in {repo}: {exc}") from exc
That is minimal, keeps CompletedProcess[str], and matches the module's documented design — observation guards degrade, unguarded paths fail typed. Making such paths actually work (errors="surrogateescape") is a larger call, because surrogates would then flow into journal/JSON writes that are themselves UTF-8; that deserves its own decision.
Pre-existing; not introduced by #373 or #376.
Found while sweeping for other copies of #374. This is the same genus but at the
_run_gitchokepoint, which makes it strictly more severe: the exception escapes untyped, defeating the very taxonomy that function exists to provide.The defect
verify._run_gitdecodes git's stdout withtext=True(locale codec,errors='strict') and translates only two exception classes:Its own docstring states the contract:
A
UnicodeDecodeErroris a third such failure, raised insidesubprocess.runbefore any return code exists, and it is not in the taxonomy — so it does exactly what the docstring says must not happen.Why ordinary git commands are safe (and which are not)
Git quotes non-ASCII paths by default (
core.quotePath), sogit status --porcelainreturns pure ASCII (?? "weird-\377-name.txt") and decodes fine.-zdisables that quoting and emits raw path bytes — which is exactly what_git_rawexists for:Reproduction
A repo containing one file whose name is not valid UTF-8 (POSIX filenames are arbitrary bytes):
Affected call sites
All three
-zinvocations:verify.py:779status --porcelain -z -uallindirty_pathsclean_incoming_collisions(merge pre-flight)verify.py:804diff --name-only -zinbranch_incoming_pathsclean_incoming_collisionsverify.py:2013ls-files -zincommit_pathsdecisions.py:161,cli.py:1859clean_incoming_collisionsraisesGitErrordeliberately when it finds stray paths, so its callers guard onGitError— and this fault sails straight past that guard.Suggested fix
Add the decode fault to the existing taxonomy rather than trying to make non-UTF-8 paths work:
That is minimal, keeps
CompletedProcess[str], and matches the module's documented design — observation guards degrade, unguarded paths fail typed. Making such paths actually work (errors="surrogateescape") is a larger call, because surrogates would then flow into journal/JSON writes that are themselves UTF-8; that deserves its own decision.Pre-existing; not introduced by #373 or #376.