Repository navigation
fix: cancel interrupted foreground session prompts - #982
saariuslystoned wants to merge 2 commits into
Conversation
Co-authored-by: saariuslystoned <159389674+saariuslystoned@users.noreply.github.com>
|
🦞👀 Pull request received. I will update this pull request when review starts. ClawSweeper review completeClawSweeper finished reviewing this revision. The review result is being finalized. |
|
Codex review: needs maintainer review before merge. What this changesThis PR forwards foreground interruption to the invocation’s exact queued or active prompt while preserving the shared owner. Example: Two callers share session capture; the second queues echo should-not-run and receives SIGINT.
Review scores
ProductKind: Bug fix · Worth it: Yes · Fix scope: Complete Merge readiness✅ Ready for maintainer review Keep this PR open for landing: current main lacks foreground request cancellation, both prior review items are addressed, and no actionable defects remain. Priority: P2 Before mergeNone. FindingsNone. Agent review detailsHow this fits togetherThe CLI submits persistent prompts to a detached session owner over queue IPC; the owner serializes work and sends ACP requests to the adapter. flowchart LR
A[Foreground CLI] --> B[Prompt interrupt handler]
B --> C[Queue IPC]
C --> D[Shared session owner]
D --> E[ACP adapter]
F[Other callers] --> D
Technical reviewBest possible solution: Land the focused CLI interruption repair using the existing targeted queue controls. Do we have a high-confidence way to reproduce the issue? Main’s foreground handler lacks interruption forwarding, and contributor captures compare unchanged-base continuation with exact-head targeted cancellation; this review did not execute code. Is this the best way to solve the issue? The patch repairs the CLI boundary using established request identity and queue cancellation rather than changing shared-owner lifecycle or observer semantics. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning medium; reviewed against af67042fac73. Provenance checked
TestingProof path: shipped entry point. SecurityNone. EvidenceWhat I checked:
Likely related people:
Preflight sourceSource unknown. Source comparison at the observed PR snapshot only. Completion is self-reported; body, policy, tests, execution, and report bytes are not verified. Review metrics
LabelsLabel changes:
Label justifications:
Rating scale6/6 🦀 challenger crab · 5/6 🦞 diamond lobster · 4/6 🐚 platinum hermit · 3/6 🦐 gold shrimp · 2/6 🦪 silver shellfish · 1/6 🧂 unranked krab. Overall follows the weaker of proof and patch quality; ✨ marks media proof (a screenshot, video, or linked artifact) that directly shows the changed behavior. WorkflowClawSweeper edits this one comment on every review. Comment HistoryReview history (1 earlier review cycle)
Reviewed November 7, 2026, 10:15 PM ET / November 8, 2026, 03:15 UTC (Revision 2). |
Co-authored-by: saariuslystoned <159389674+saariuslystoned@users.noreply.github.com>
|
@clawsweeper re-review Both published review items are addressed at |
|
🦞👀 Re-review progress:
|
What Problem This Solves
Interrupting a foreground session prompt exits its client while its queued or active work can continue in the shared owner.
User Impact
Foreground interruption now targets that invocation's request. Pending work is removed; active work receives cooperative cancellation while other clients' turns and the shared owner survive.
sessions watchand accepted--no-waitwork retain detach behavior. When admission or cancellation control times out, the message reports an unknown outcome and tells the caller to inspect the same session withstatus,sessions history, orsessions watchbefore resubmitting.Why This Change Was Made
The CLI assigns a request ID and forwards interruption after queue acknowledgement through existing targeted queue controls. Waiting is bounded; it does not force-kill the shared owner or fall back to cancelling an unrelated request. No new public flags or session-record format are introduced.
Evidence
Base:
af67042fac736e8c1d0cd9dd4b8057fcee85d46d. Current head:c11bdbe170ab38dc70e5c31535c40d4ae0334251(original fix plus the two-file diagnostic follow-up requested in the published review).Current-head local validation passed: 3,186 tests, 3,170 passed, 16 skipped, zero failed/cancelled; all 247 separate coverage cases and declared 85% coverage thresholds passed. The coverage gate measures its declared source set, not the whole repository. Focused helper tests (10), real-CLI signal integration cases (3), build and docs also passed.
Commands:
pnpm run build pnpm run build:test node --test dist-test/test/prompt-interruption.test.js node --test --test-name-pattern='foreground SIGINT targets only its own request' dist-test/test/integration.test.js pnpm run check pnpm run check:docsIndependent comprehensive P0–P3 review of the entire current base/head diff is scoped-clean with zero findings; the separate two-file follow-up review is also clean. The comprehensive static reviewer lacked unchanged submission/IPC source context and did not execute tests; actual behavior and full-test evidence are separate above/below. GitHub CI and fresh hosted ClawSweeper review remain pending on this exact head. The old blocked verdict applies to the prior head; its two requested repairs are implemented and captured here. No merge-readiness claim.
Inspectable behavior captures
These are captured outputs from the built
dist/cli.js, its production shared-owner/queue IPC path, and a disposable repository mock ACP peer on Linux Node 26.13.0. The stalled-control case instead uses an owned synthetic IPC listener to withhold completion. Absolute disposable fixture paths are replaced by/FIXTURE; session/request IDs are synthetic. The blocks preserve protocol content and exit observations, not private state or leases. No native-provider, Windows, physical terminal-keypress, or production behavior is claimed.The active PID and process-group replays each observed one targeted ACP cancel and exit 130, then a successful same-owner successor. Exit 130 alone is not an adapter completion verdict; inspect the history and wire evidence. The queued replay waited for queue depth 1 before signalling its caller; depth became 0, the unrelated active caller stayed alive and completed normally on release, no adapter cancellation occurred, and the forbidden queued prompt never reached the peer. All observed peer processes were gone after the test sessions closed.
Captured active PID and process-group interruption
Actual foreground stdout, PID SIGINT
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":true,"writeTextFile":true},"terminal":true},"clientInfo":{"name":"acpx","version":"0.22.0"}}} {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":1,"authMethods":[],"agentCapabilities":{"loadSession":true,"promptCapabilities":{"image":true,"audio":true,"embeddedContext":true}}}} {"jsonrpc":"2.0","id":1,"method":"session/load","params":{"sessionId":"963a4900-7fbe-4870-91c8-8bda7201fcfc","cwd":"/FIXTURE","mcpServers":[]}} {"jsonrpc":"2.0","id":1,"result":{}} {"jsonrpc":"2.0","id":2,"method":"session/prompt","params":{"sessionId":"963a4900-7fbe-4870-91c8-8bda7201fcfc","prompt":[{"type":"text","text":"stream-wait-file /FIXTURE/release-first"}]}} {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"963a4900-7fbe-4870-91c8-8bda7201fcfc","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"flow-held"}}}} {"jsonrpc":"2.0","method":"session/cancel","params":{"sessionId":"963a4900-7fbe-4870-91c8-8bda7201fcfc"}}Captured outcome fields (both signal deliveries)
{ "pid": { "source": "c11bdbe170ab38dc70e5c31535c40d4ae0334251", "foreground_after_sigint": 130, "cancel_messages_after_sigint": 1, "explicit_foreground_code": 0, "explicit_cancel_messages": 1 }, "process_group": { "source": "c11bdbe170ab38dc70e5c31535c40d4ae0334251", "foreground_after_sigint": 130, "cancel_messages_after_sigint": 1, "explicit_foreground_code": 0, "explicit_cancel_messages": 1 } }Actual same-owner successor stdout
{"jsonrpc":"2.0","id":3,"method":"session/prompt","params":{"sessionId":"963a4900-7fbe-4870-91c8-8bda7201fcfc","prompt":[{"type":"text","text":"echo successor"}]}} {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"963a4900-7fbe-4870-91c8-8bda7201fcfc","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"successor"}}}} {"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}Actual history after interrupt
{"id":"bb59455d-b467-48d5-b932-f7051bd29ad8","sessionId":"963a4900-7fbe-4870-91c8-8bda7201fcfc","limit":20,"count":4,"entries":[{"role":"user","timestamp":"2026-11-08T02:52:00.961Z","textPreview":"stream-wait-file /FIXTURE/release-first"},{"role":"assistant","timestamp":"2026-11-08T02:52:00.961Z","textPreview":"flow-held"},{"role":"user","timestamp":"2026-11-08T02:52:00.961Z","textPreview":"echo successor"},{"role":"assistant","timestamp":"2026-11-08T02:52:00.961Z","textPreview":"successor"}]}Captured queued cancellation and unrelated active-turn survival
Fixture observations and captured CLI command output
{"event": "command", "args": ["sessions", "new", "--name", "capture"], "exit": 0, "stdout": "{\"action\":\"session_ensured\",\"created\":true,\"acpxRecordId\":\"51bc70a5-8135-4fa3-9629-9dd82c1bd30a\",\"acpxSessionId\":\"38df35f6-6764-412f-accd-369b8ca91b35\",\"name\":\"capture\"}\n", "stderr": "[acpx] created session capture (51bc70a5-8135-4fa3-9629-9dd82c1bd30a)\n[acpx] agent: codex\n[acpx] cwd: /FIXTURE\n"} {"event": "foreground_started", "role": "active", "pid": 1312534, "prompt": "stream-wait-file /FIXTURE/release-active"} {"event": "foreground_started", "role": "queued", "pid": 1312862, "prompt": "echo should-not-run"} {"event": "queued_admitted", "queue_depth": 1, "active_exit": null} {"event": "after_queued_SIGINT", "queued_exit": 130, "active_exit": null, "adapter_cancels": 0, "queue_depth": 0} {"event": "unrelated_released_normally", "active_exit": 0} {"event": "command", "args": ["prompt", "-s", "capture", "echo successor"], "exit": 0, "stdout": "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"session/prompt\",\"params\":{\"sessionId\":\"38df35f6-6764-412f-accd-369b8ca91b35\",\"prompt\":[{\"type\":\"text\",\"text\":\"echo successor\"}]}}\n{\"jsonrpc\":\"2.0\",\"method\":\"session/update\",\"params\":{\"sessionId\":\"38df35f6-6764-412f-accd-369b8ca91b35\",\"update\":{\"sessionUpdate\":\"agent_message_chunk\",\"content\":{\"type\":\"text\",\"text\":\"successor\"}}}}\n{\"jsonrpc\":\"2.0\",\"id\":3,\"result\":{\"stopReason\":\"end_turn\"}}\n", "stderr": "[acpx] session capture (51bc70a5-8135-4fa3-9629-9dd82c1bd30a) \u00b7 /FIXTURE \u00b7 agent connected\n"} {"event": "queued_adapter_dispatch_observed", "value": false} {"event": "command", "args": ["sessions", "history", "capture"], "exit": 0, "stdout": "{\"id\":\"51bc70a5-8135-4fa3-9629-9dd82c1bd30a\",\"sessionId\":\"38df35f6-6764-412f-accd-369b8ca91b35\",\"limit\":20,\"count\":4,\"entries\":[{\"role\":\"user\",\"timestamp\":\"2026-11-08T02:53:19.216Z\",\"textPreview\":\"stream-wait-file /FIXTURE/release-active\"},{\"role\":\"assistant\",\"timestamp\":\"2026-11-08T02:53:19.216Z\",\"textPreview\":\"flow-heldstream-wait-file done: flow-held\"},{\"role\":\"user\",\"timestamp\":\"2026-11-08T02:53:19.216Z\",\"textPreview\":\"echo successor\"},{\"role\":\"assistant\",\"timestamp\":\"2026-11-08T02:53:19.216Z\",\"textPreview\":\"successor\"}]}\n", "stderr": ""} {"event": "command", "args": ["sessions", "close", "capture"], "exit": 0, "stdout": "{\"action\":\"session_closed\",\"acpxRecordId\":\"51bc70a5-8135-4fa3-9629-9dd82c1bd30a\",\"acpxSessionId\":\"38df35f6-6764-412f-accd-369b8ca91b35\"}\n", "stderr": ""}Complete synthetic ACP request trace for that session
{"pid":1312305,"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":true,"writeTextFile":true},"terminal":true},"clientInfo":{"name":"acpx","version":"0.22.0"}}} {"pid":1312305,"jsonrpc":"2.0","id":1,"method":"session/new","params":{"cwd":"/FIXTURE","mcpServers":[]}} {"pid":1312646,"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":true,"writeTextFile":true},"terminal":true},"clientInfo":{"name":"acpx","version":"0.22.0"}}} {"pid":1312646,"jsonrpc":"2.0","id":1,"method":"session/load","params":{"sessionId":"38df35f6-6764-412f-accd-369b8ca91b35","cwd":"/FIXTURE","mcpServers":[]}} {"pid":1312646,"jsonrpc":"2.0","id":2,"method":"session/prompt","params":{"sessionId":"38df35f6-6764-412f-accd-369b8ca91b35","prompt":[{"type":"text","text":"stream-wait-file /FIXTURE/release-active"}]}} {"pid":1312646,"jsonrpc":"2.0","id":3,"method":"session/prompt","params":{"sessionId":"38df35f6-6764-412f-accd-369b8ca91b35","prompt":[{"type":"text","text":"echo successor"}]}}Actual unrelated active caller stdout
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":true,"writeTextFile":true},"terminal":true},"clientInfo":{"name":"acpx","version":"0.22.0"}}} {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":1,"authMethods":[],"agentCapabilities":{"loadSession":true,"promptCapabilities":{"image":true,"audio":true,"embeddedContext":true}}}} {"jsonrpc":"2.0","id":1,"method":"session/load","params":{"sessionId":"38df35f6-6764-412f-accd-369b8ca91b35","cwd":"/FIXTURE","mcpServers":[]}} {"jsonrpc":"2.0","id":1,"result":{}} {"jsonrpc":"2.0","id":2,"method":"session/prompt","params":{"sessionId":"38df35f6-6764-412f-accd-369b8ca91b35","prompt":[{"type":"text","text":"stream-wait-file /FIXTURE/release-active"}]}} {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"38df35f6-6764-412f-accd-369b8ca91b35","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"flow-held"}}}} {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"38df35f6-6764-412f-accd-369b8ca91b35","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"stream-wait-file done: flow-held"}}}} {"jsonrpc":"2.0","id":2,"result":{"stopReason":"end_turn"}}Captured stalled accepted-cancel control and bounded CLI exit
Actual CLI -> owned synthetic IPC server requests
{"type": "submit_prompt", "requestId": "d2f83560-07d3-44fb-9ec8-436952584cf6", "ownerGeneration": 101010, "message": "synthetic transport-stall control", "prompt": [{"type": "text", "text": "synthetic transport-stall control"}], "permissionMode": "approve-reads", "nonInteractivePermissions": "deny", "suppressSdkConsoleErrors": false, "promptRetries": 0, "waitForCompletion": true, "sessionOptions": {}} {"type": "cancel_prompt", "requestId": "fbe4fe18-20ac-408f-b0c8-342f141a1ddc", "ownerGeneration": 101010, "targetRequestId": "d2f83560-07d3-44fb-9ec8-436952584cf6"}Actual synthetic IPC acceptance responses; server intentionally withholds completion
{"type": "accepted", "requestId": "d2f83560-07d3-44fb-9ec8-436952584cf6", "ownerGeneration": 101010} {"type": "accepted", "requestId": "fbe4fe18-20ac-408f-b0c8-342f141a1ddc", "ownerGeneration": 101010}Actual CLI stdout
{"jsonrpc":"2.0","id":null,"error":{"code":-32603,"message":"Timed out cancelling foreground prompt; request outcome is unknown. Inspect the same session with status, sessions history, or sessions watch before resubmitting.","data":{"acpxCode":"RUNTIME","origin":"cli","sessionId":"unknown"}}}Captured process observations before fixture socket closure
{ "source": "c11bdbe170ab38dc70e5c31535c40d4ae0334251", "cancel_request_arrived": true, "returncode_four_seconds_after_cancel_admission": 1, "request_types": [ "submit_prompt", "cancel_prompt" ], "returncode_after_fixture_socket_close": 1, "child_gone": true }The committed regression cases cover request identity, repeated signals, admission/completion races, cancellation refusal, bounded stalls, queued isolation and actual signal delivery. A stale hardcoded timestamp in the original disposable stall fixture triggered owner recovery during replay; that failed fixture run is preserved. The separately named fixture uses the current timestamp and captures both IPC directions. Product source was unchanged by this fixture correction.
AI-assisted contribution: RoboClaw implementation; independently reproduced and validated with Codex. Maintainer edits are enabled. Human retains merge authority.