fix(terminal): name shell-level exit codes 126/127 (Closes #1452) - #1472
Merged
Conversation
Child 1 of #1371 (terminal 27.4% failure rate across 673 sessions). _interpret_exit_code already explains per-command exit codes (grep 1 = no matches, curl 7 = connection failed) and signal codes (137 = SIGKILL). It said nothing about 126 and 127, because those are not per-command: they are about the INVOCATION. 127 means the shell never found anything to run, whatever the command was, so it falls through the per-command table and produced no note. Those two are worth naming precisely because the failure is not in the command's logic. Re-running the same string cannot succeed, which is the retry-spiral shape behind the parent issue's numbers. Each note therefore states the cause AND what to change: 126 -> found but not executable; check permissions (chmod +x) or whether it is a directory. Re-running unchanged will fail identically. 127 -> not found; check spelling, or whether the tool is installed and on PATH. Re-running unchanged will fail identically; try `which` or an absolute path. Ordering matters and is tested: per-command semantics and signal codes are matched BEFORE this fallback, so `grep x` exit 1 still reads "No matches found" rather than a generic shell hint, and an unknown code still returns None instead of a fabricated explanation. 8 new tests; 39 passing in test_terminal_exit_semantics.py; ruff clean. Closes #1452
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Child 1 of #1371 (terminal 27.4% failure rate across 673 sessions — the dominant failure signal).
The gap
_interpret_exit_codealready explains per-command exit codes (grep1 = no matches,curl7 = connection failed) and signal codes (137 = SIGKILL). It said nothing about 126 and 127, because those are not per-command — they are about the invocation. 127 means the shell never found anything to run, whatever the command was, so it falls straight through the per-command table and produced no note at all.Why these two specifically
The failure is not in the command's logic. Re-running the same string cannot succeed — which is exactly the retry-spiral shape behind the parent issue's numbers. So each note states the cause and what to change:
chmod +x) or whether it is a directory. Re-running unchanged will fail identically.whichor an absolute path.Ordering is the risk, and it is tested
The new lookup is a fallback, matched after the per-command table and the signal codes. Tests pin this:
grep x fexit 1 still reads "No matches found (not an error)", not a generic shell hintcurlexit 7 still reads "Failed to connect to host"sleepexit 137 still reads "SIGKILL"Nonerather than a fabricated explanation8 new tests, 39 passing in
test_terminal_exit_semantics.py, 345 across the terminal suites; ruff clean.Scope
Deliberately just this. Children 2 (#1453, strategy-switch after 3 consecutive failures) and 3 (#1454, security-scan block directive) are separate — and #1453 in particular needs checking against
loop_guard, which already fires on the third consecutive terminal failure today.