Skip to content

fix(environment-provider): refuse a command result that carries no exit status - #241

Merged
drewstone merged 1 commit into
mainfrom
fix/exec-exit-status
Aug 21, 2026
Merged

fix(environment-provider): refuse a command result that carries no exit status#241
drewstone merged 1 commit into
mainfrom
fix/exec-exit-status

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Plan

Problem - execResultFromUnknown read an absent exit status as 0. Exit zero is the one value that means a command succeeded, so a sandbox SDK answering with captured output and no exitCode or code produced exec() success for a command whose outcome was never measured, and the turn over it finished status: "completed".

Change - a result with no finite exit status is refused, naming the SDK call whose answer could not be read.

Why long-term right - it is the house rule these repos already hold: no fallbacks, fail loud, missing data stays unknown rather than becoming a default. Round 1 collapsed three copies of this reader into one owner, so the defect that needed three fixes now needs one, and no adapter can drift back to it.

Cost - 7 files. execResultFromUnknown gains a required source argument; it is unreleased (npm serves agent-interface@1.4.0; it ships first in the pending 1.5.0), so no consumer can be holding the old signature. Rollback is a revert.

Proof

The bug, in a real adapter flow. An E2B sandbox whose commands.run reports a failed build and no exit status, run through provider.create() then environment.stream():

origin/main   turn reported: {"finalText":"npm ERR! build failed\n","status":"completed","exitCode":0,"stderr":""}
this branch   turn refused:  E2B sandbox commands.run returned no exit status:
                             a command result must carry a finite exitCode or code

A failing build read as a build that worked. That is the whole defect.

The round-1 test caught the change before I did. execResultFromUnknown had a test asserting the silent zero (expect(...).toEqual({ exitCode: 0, ... })). It went red on the first run of this change, which is what that test was for; it is now rewritten to assert the refusal.

gate result
pnpm build 9/9 packages Done
pnpm check-types 9/9 packages Done
pnpm test 91 files, 1544 passed, 0 failed (was 1542 on origin/main)
pnpm check:package-artifacts 9 packages, 31 exports, 62 export targets
pnpm check:control-artifacts 3 packed contracts passed; 7 files / 145 tests

All three adapters' conformance runs (runAgentEnvironmentProviderConformance) pass unchanged - their SDKs do return an exit status, which is why this stayed invisible.

Simplification

Simplification: none - this is a behavior fix on an owner that round 1 had already collapsed. The three adapters each pass the name of their own SDK call, so the refusal names the fix site rather than the shared reader.
Net: +63 / -19 lines, 7 files, 0 copies removed (the copies were removed in #234; this is the one-line fix that collapse made possible).
Not done here: commandTurnEvents still reads exitCode: result?.exitCode ?? 1 and stdout ?? "" for the case where an adapter exposes no exec at all. That path is a different question - an environment that cannot run commands should arguably refuse the turn at the top rather than synthesize a failed result - and it needs its own decision about what a workspace-only adapter without exec is for.
Tests: +2 net, 1 rewritten (that a result with no exit status is refused, covering nothing at all, output-without-status, a non-numeric status, and a non-finite status - the failure mode is a failed command reported as a completed turn; that a zero exit status is still accepted, because zero is a measured success and must not be caught by the same check; the wrong-type test kept, narrowed to captured output only), -0 deleted.

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — aa9e798e

This PR was opened by the trusted drewstone account.

This approval is provisional and was applied by the local stand-in because the pr-reviewer webhook host is unreachable (2026-08-21). CI on this head is fully green. The full PR reviewer audit re-runs via the resweep when the service returns and will publish findings if it detects issues.

@drewstone
drewstone merged commit bd76ed8 into main Aug 21, 2026
2 checks passed
@drewstone
drewstone deleted the fix/exec-exit-status branch August 21, 2026 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants