fix: validated turn result outranks child exit code in headless work-on - #140
Merged
Conversation
…anscript # Conflicts: # CHANGELOG.md
…code on predicate rejection The merge of origin/master folded this branch's Unreleased bullets into v0.125.1, which is already tagged and released, leaving no Unreleased section for the release watcher to cut. Split them back out; everything from v0.125.1 down is byte-identical to master. Also address two code-review findings in runDetachedTurn: - a non-zero child exit is now logged when the output parsed but failed a predicate, mirroring the existing override log rather than dropping the signal silently - document why the json.Unmarshal error is flattened into the message instead of chained, so the sentinel stays errors.Is-matchable
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.
Fixes a defect where
vault-cli work-ondiscarded headless turns that had actually succeeded.The bug
runDetachedTurncaptures the child's--output-format jsonblob to a temp file and validates it viavalidateSessionTurn— but only on the clean-exit path. TheexitErr != nilbranch returned before the read, so a turn that produced a complete, valid result was thrown away on the strength of the process exit code alone. The task'sclaude_session_idwas then removed by the compensating clear, stranding the transcript where the Vault UI could no longer reach it.Observed 2026-09-06: a turn ran ~2 minutes, ended
stop_reason: end_turnwith zero errors and zero permission denials, and the operator got a ~40-line Go stack whose only content wasexit status 1.Why this is a contract change, not a drift fix
docs/work-on-session-lifecycle.mddocumented the old behaviour as intended — "a non-zero child exit ... return[s] an error, so the caller persists nothing". The doc's own justification is what condemns it: it defends validation because offering a Resume that cannot work lies to the operator. Discarding a session that can be resumed is the same lie inverted, and costs more. The doc changes with the code.The exit code is also the weaker signal by construction — stderr goes to
os.DevNull, so it arrives with no explanation, while the result blob is structured and already validated.What changed
resulttext and names the failed predicate.errClaudeOutputUnparseablesentinel.Executeon real vault files, on both the task path andgoal_workon.go's separatehandleClaudeSession.Regression lock
The obvious refactor — hoisting
os.ReadFileabove theselectso both branches share it — would let a timed-out child's partial blob validate as success, persisting a resumable id for a turn that is still running. That is strictly worse than the bug being fixed. A test seeds a valid blob into the timeout path and asserts the error still fires; the read is pinned inside the child-exited branch.Out of scope
Why a clean-
end_turnchild exits 1 at all is unresolved and deliberately excluded — the point of this fix is that vault-cli must not depend on the exit code being trustworthy. Ruled out during triage: router reachability, CLI flags,--max-turns(inert), and session-id collision (a different, instant failure).Spec:
specs/in-progress/045-bug-exit-code-outranks-validated-turn.md