You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Windows-only, and silent in both directions. Found while getting the Claude Code and Codex hook bridge suites to run on a Windows host. Present in 0.1.0-rc.8.
What happens
On Windows the shell seam resolves to pwsh — packages/bundle/base/cordis.patch.yml disables bash-sandbox on win32 and pwsh-sandbox everywhere else — so ctx.shell is PwshLocalExecutor.
powershell -Command <body> does not adopt a native command's exit code. It reports 1 for any failure. The one-shot executor passes the body straight through:
so every non-zero status arrives as 1 and the exact code is lost.
Measured directly, a body exiting 2:
invocation
reported status
powershell -Command "node exits-2.js"
1
same, with a status epilogue appended
2
Why it matters more than a wrong number
Both hook bridges read exit 2 as block — deny this tool, block this prompt, steer this stop — and every other non-zero exit as a non-blocking hook error.
Collapsed to 1, a hook that means "deny" reads as "the hook errored, carry on", and the denied tool runs anyway. Nothing is logged, and the block simply does not happen. A user who writes a PreToolUse hook to deny a dangerous command gets no denial and no indication that their hook was ignored.
The same loss reaches the model for ordinary commands: every failing command reports exit 1, so distinctions the model reasonably acts on — grep's 1 (no matches) versus 2 (error), a test runner's specific codes — are gone on Windows.
The repo already solves this, one layer over
packages/shell/tool-pwsh-persistent wraps its body so the persistent path reports the exact native code:
and its Agent Note states the contract plainly: the exact native exit code, 1 for a terminating PowerShell error, 0 for success. The one-shot executor has no equivalent, so the two paths disagree about what a status means depending on which shell stack a preset mounts.
Suggested fix
Give the one-shot executor the same epilogue, in argv() — which is also the seam pwsh-sandbox confines through, so the sandboxed executor inherits it unchanged:
Running the body inline rather than through Invoke-Expression means no quoting pass is needed and the body's own syntax errors still surface as PowerShell's.
One detail worth copying: put each clause on its own line rather than after a semicolon. A body ending in a # comment otherwise comments the epilogue out and silently restores the collapsed status — in exactly the case the epilogue exists to protect. Hook commands are user-authored text, so a trailing # why this hook exists is ordinary.
Verified against a real pwsh across the arms that matter: a body exiting 2 reports 2, a body exiting 0 reports 0, a failing cmdlet that sets no $LASTEXITCODE still reports 1, and a body with a trailing comment reports 2.
Happy to share the patch — noting we cannot open a pull request, per CONTRIBUTING.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Windows-only, and silent in both directions. Found while getting the Claude Code and Codex hook bridge suites to run on a Windows host. Present in 0.1.0-rc.8.
What happens
On Windows the shell seam resolves to pwsh —
packages/bundle/base/cordis.patch.ymldisablesbash-sandboxon win32 andpwsh-sandboxeverywhere else — soctx.shellisPwshLocalExecutor.powershell -Command <body>does not adopt a native command's exit code. It reports1for any failure. The one-shot executor passes the body straight through:so every non-zero status arrives as
1and the exact code is lost.Measured directly, a body exiting 2:
powershell -Command "node exits-2.js"Why it matters more than a wrong number
Both hook bridges read exit
2as block — deny this tool, block this prompt, steer this stop — and every other non-zero exit as a non-blocking hook error.Collapsed to
1, a hook that means "deny" reads as "the hook errored, carry on", and the denied tool runs anyway. Nothing is logged, and the block simply does not happen. A user who writes a PreToolUse hook to deny a dangerous command gets no denial and no indication that their hook was ignored.The same loss reaches the model for ordinary commands: every failing command reports exit 1, so distinctions the model reasonably acts on — grep's 1 (no matches) versus 2 (error), a test runner's specific codes — are gone on Windows.
The repo already solves this, one layer over
packages/shell/tool-pwsh-persistentwraps its body so the persistent path reports the exact native code:and its Agent Note states the contract plainly: the exact native exit code,
1for a terminating PowerShell error,0for success. The one-shot executor has no equivalent, so the two paths disagree about what a status means depending on which shell stack a preset mounts.Suggested fix
Give the one-shot executor the same epilogue, in
argv()— which is also the seampwsh-sandboxconfines through, so the sandboxed executor inherits it unchanged:Running the body inline rather than through
Invoke-Expressionmeans no quoting pass is needed and the body's own syntax errors still surface as PowerShell's.One detail worth copying: put each clause on its own line rather than after a semicolon. A body ending in a
#comment otherwise comments the epilogue out and silently restores the collapsed status — in exactly the case the epilogue exists to protect. Hook commands are user-authored text, so a trailing# why this hook existsis ordinary.Verified against a real pwsh across the arms that matter: a body exiting 2 reports 2, a body exiting 0 reports 0, a failing cmdlet that sets no
$LASTEXITCODEstill reports 1, and a body with a trailing comment reports 2.Happy to share the patch — noting we cannot open a pull request, per CONTRIBUTING.
All reactions