Summary
ENV_FAULT_RCS = frozenset({126, 127}) (src/bmad_loop/verify.py:1376) encodes the sh launcher convention: 126 = found but not executable, 127 = not found. run_verify_commands runs commands with shell=True, so on Windows the shell is cmd, which never uses those codes — a missing executable is 9009 ("is not recognized as an internal or external command") and a non-executable one typically surfaces as 1.
The consequence is that on Windows the env-fault arm at verify.py:1410 is unreachable in practice. A broken verify environment is classified an ordinary fixable failure, so the run dispatches repair sessions and burns dev attempts against a tree no session can fix — precisely the failure #126 was filed for and #130 fixed on POSIX.
The comment at verify.py:1374-1375 states the gap ("Windows cmd signals these as 9009/1 instead, so it keeps the charged behavior") but nothing tracks closing it.
Evidence
The three engine tests that pin this behavior are all win32-skipped for exactly this reason:
test_review_verify_env_fault_escalates_instead_of_fix_session
test_skip_review_env_fault_escalates_not_defers
test_fix_phase_env_fault_escalates_instead_of_looping
each carrying @pytest.mark.skipif(sys.platform == "win32", reason="sh env-fault exit codes (cmd reports 9009)"). So the Windows behavior is neither correct nor exercised.
Why this is not a trivial patch
Adding 9009 is safe and unambiguous. Adding 1 is not — under cmd, rc 1 is also the ordinary "the tests failed" code, which is exactly the fixable failure that should route to a repair session. Classifying it an env fault would pause runs on every genuine test failure on Windows.
So the fix needs a decision on how far to go, roughly:
- 9009 only — narrow, no false positives, catches the common "tool not on PATH" case, leaves non-executable files charged.
- 9009 + a stderr probe — treat rc 1 as an env fault only when the output tail matches cmd's not-recognized/access-denied phrasing. Catches more, but pattern-matches localized shell text.
- Bypass the shell for env detection — resolve the command's leading token before running it and classify unresolvable ones without relying on the shell's exit code at all.
Whatever is chosen, ENV_FAULT_RCS stops being a platform-neutral frozenset and becomes a per-shell classifier; the three skips above should be lifted as part of the change so the Windows path is actually pinned.
Refs
Summary
ENV_FAULT_RCS = frozenset({126, 127})(src/bmad_loop/verify.py:1376) encodes the sh launcher convention: 126 = found but not executable, 127 = not found.run_verify_commandsruns commands withshell=True, so on Windows the shell iscmd, which never uses those codes — a missing executable is 9009 ("is not recognized as an internal or external command") and a non-executable one typically surfaces as 1.The consequence is that on Windows the env-fault arm at
verify.py:1410is unreachable in practice. A broken verify environment is classified an ordinary fixable failure, so the run dispatches repair sessions and burns dev attempts against a tree no session can fix — precisely the failure #126 was filed for and #130 fixed on POSIX.The comment at
verify.py:1374-1375states the gap ("Windows cmd signals these as 9009/1 instead, so it keeps the charged behavior") but nothing tracks closing it.Evidence
The three engine tests that pin this behavior are all win32-skipped for exactly this reason:
test_review_verify_env_fault_escalates_instead_of_fix_sessiontest_skip_review_env_fault_escalates_not_deferstest_fix_phase_env_fault_escalates_instead_of_loopingeach carrying
@pytest.mark.skipif(sys.platform == "win32", reason="sh env-fault exit codes (cmd reports 9009)"). So the Windows behavior is neither correct nor exercised.Why this is not a trivial patch
Adding 9009 is safe and unambiguous. Adding 1 is not — under
cmd, rc 1 is also the ordinary "the tests failed" code, which is exactly the fixable failure that should route to a repair session. Classifying it an env fault would pause runs on every genuine test failure on Windows.So the fix needs a decision on how far to go, roughly:
Whatever is chosen,
ENV_FAULT_RCSstops being a platform-neutral frozenset and becomes a per-shell classifier; the three skips above should be lifted as part of the change so the Windows path is actually pinned.Refs