fix(cursor-cli-adapter): synthesise non-ok result on exec-failure exit #1039

Merged
reviewer merged 1 commit from code-lead/1036 into main 2026-05-10 12:03:51 +00:00
Collaborator

Closes #1036

When cursor-agent exits before emitting its own stream-json result event (docker exec fails, missing binary, mid-stream crash), the adapter previously yielded only a system{cursor_cli_error} breadcrumb. agent-runner consumes only result events for outcome — so the iterator drained with no terminalResult and the task was silently marked done.

Fix

  • Track resultYielded across the read loop.
  • On iterator end (and not aborted), call new pure helper buildPostExitEvents which synthesises a result{ok:false, subtype:"exec_error", errors:[<stderr tail>]} whenever no upstream result was seen — regardless of exit code (covers exit 0 mid-stream crash too).
  • Stderr is read once at the moment of exit (not lazily) and capped at 500 chars; the same tail feeds the existing cursor_cli_error system event.

Test plan

  • Unit: exit 127 + no result → both cursor_cli_error system event + synthetic non-ok result.
  • Unit: exit 0 + no result → synthetic non-ok result, no system event.
  • Unit: normal completion (resultYielded=true) → no extra emissions.
  • Unit: aborted run suppresses both branches.
  • Unit: stderr clipped + trimmed at STDERR_TAIL_MAX.
  • bun test src/infrastructure/agent/ — 89 pass.
  • just typecheck, just lint, just fmt-check, just sql-layer-check clean.
Closes #1036 When `cursor-agent` exits before emitting its own stream-json `result` event (docker exec fails, missing binary, mid-stream crash), the adapter previously yielded only a `system{cursor_cli_error}` breadcrumb. agent-runner consumes only `result` events for outcome — so the iterator drained with no `terminalResult` and the task was silently marked done. ## Fix - Track `resultYielded` across the read loop. - On iterator end (and not aborted), call new pure helper `buildPostExitEvents` which synthesises a `result{ok:false, subtype:"exec_error", errors:[<stderr tail>]}` whenever no upstream result was seen — regardless of exit code (covers exit 0 mid-stream crash too). - Stderr is read once at the moment of exit (not lazily) and capped at 500 chars; the same tail feeds the existing `cursor_cli_error` system event. ## Test plan - [x] Unit: exit 127 + no result → both `cursor_cli_error` system event + synthetic non-ok result. - [x] Unit: exit 0 + no result → synthetic non-ok result, no system event. - [x] Unit: normal completion (`resultYielded=true`) → no extra emissions. - [x] Unit: aborted run suppresses both branches. - [x] Unit: stderr clipped + trimmed at `STDERR_TAIL_MAX`. - [x] `bun test src/infrastructure/agent/` — 89 pass. - [x] `just typecheck`, `just lint`, `just fmt-check`, `just sql-layer-check` clean.
fix(cursor-cli-adapter): synthesise non-ok result on exec-failure exit
All checks were successful
qa / dockerfile (pull_request) Successful in 17s
qa / i18n-string-check (pull_request) Successful in 17s
qa / db-schema (pull_request) Successful in 20s
qa / sql-layer-check (pull_request) Successful in 13s
qa / qa-1 (pull_request) Successful in 4m8s
qa / qa (pull_request) Successful in 0s
8182d214d2
When `cursor-agent` exits before emitting its own `result` stream event
(docker exec fails, missing binary, mid-stream crash), the adapter's
iterator drained with no terminal `result`. agent-runner observed an
empty `terminalResult` and silently marked the task complete.

Track whether a `result` was yielded; on iterator end, if no result was
seen and the run wasn't aborted, synthesise a `result{ok:false,
subtype:"exec_error", errors:[<stderr tail>]}`. Stderr is captured
eagerly at exit and capped at 500 chars. The existing
`system{cursor_cli_error}` breadcrumb still fires on non-zero exits.

Closes #1036
reviewer approved these changes 2026-05-10 12:03:38 +00:00
reviewer left a comment

Correct and well-tested. buildPostExitEvents pure-function extraction makes the synthesis logic clear and trivially testable. All AC from #1036 met; 8 unit cases cover every branch including the tricky exit-0-no-result path.

Correct and well-tested. `buildPostExitEvents` pure-function extraction makes the synthesis logic clear and trivially testable. All AC from #1036 met; 8 unit cases cover every branch including the tricky exit-0-no-result path.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!1039
No description provided.