Skip to content

Multiple stop reasons from cancelled turns #551

@CSRessel

Description

@CSRessel

(Not written by AI)

I created some edge-case logic myself, to handle this bug (behavior?) in my own client: tilework-tech/nori-cli#442

I'm not totally certain if this is a bug or not, but from my best read of the ACP docs it looks like technically permissible? It never explicitly says the agent should send only one stop reason, but it does seem to imply that.

When an Agent stops a turn, it must specify the corresponding StopReason...

After all ongoing operations have been successfully aborted and pending updates have been sent, the Agent MUST respond to the original session/prompt request with the cancelled stop reason....

https://agentclientprotocol.com/protocol/prompt-turn#cancellation

However with the Claude ACP adapter specifically, any cancelled prompt turn I'm seeing will always send a second stop reason when it's prompted again.

Exact repro ACP wire during cancellation:

...
→ {"jsonrpc":"2.0","method":"session/cancel","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf"}}
← {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","update":{"sessionUpdate":"usage_update","used":24422,"size":1000000,"cost":{"amount":0.15535875,"currency":"USD"}}}}
← {"jsonrpc":"2.0","id":"c08f997e-8794-4e83-aa41-4c5ae3d3be94","result":{"stopReason":"cancelled"}}
→ {"jsonrpc":"2.0","method":"session/prompt","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","prompt":[{"type":"text","text":"what have you finished?"}]},"id":"daa0362c-edd8-47b7-ab22-091bc10a2040"}
← {"jsonrpc":"2.0","id":"daa0362c-edd8-47b7-ab22-091bc10a2040","result":{"stopReason":"end_turn","usage":{"inputTokens":0,"outputTokens":0,"cachedReadTokens":0,"cachedWriteTokens":0,"totalTokens":0}}}
→ {"jsonrpc":"2.0","method":"session/prompt","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","prompt":[{"type":"text","text":"."}]},"id":"39d50f73-65bf-4eee-9bc9-0d05527b376e"}
← {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":""}}}}
← {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"Nothing"}}}}
← {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" — I hadn't started any work yet. You asked me to run"}}}}
...

Should both of these stop reasons actually come through from the agent? I would have thought just the first one.

Full ACP Wire
→ {"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":true,"writeTextFile":true},"terminal":false,"auth":{"terminal":false}},"clientInfo":{"name":"codex","title":"Codex CLI","version":"0.0.0"}},"id":"d48faf1b-4977-455b-a03a-946eac6cb430"}
← {"jsonrpc":"2.0","id":"d48faf1b-4977-455b-a03a-946eac6cb430","result":{"protocolVersion":1,"agentCapabilities":{"_meta":{"claudeCode":{"promptQueueing":true}},"promptCapabilities":{"image":true,"embeddedContext":true},"mcpCapabilities":{"http":true,"sse":true},"loadSession":true,"sessionCapabilities":{"fork":{},"list":{},"resume":{},"close":{}}},"agentInfo":{"name":"@zed-industries/claude-agent-acp","title":"Claude Agent","version":"0.23.1"},"authMethods":[]}}
→ {"jsonrpc":"2.0","method":"session/new","params":{"cwd":"/home/clifford/Documents/source/nori/cli/.worktrees/plan-session-update-support","mcpServers":[]},"id":"4a20bf17-08e7-459b-a393-c3499020022a"}
← {"jsonrpc":"2.0","id":"4a20bf17-08e7-459b-a393-c3499020022a","result":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","models":{"availableModels":[{"modelId":"default","name":"Default (recommended)","description":"Opus 4.6 with 1M context · Most capable for complex work"},{"modelId":"sonnet","name":"Sonnet","description":"Sonnet 4.6 · Best for everyday tasks"},{"modelId":"sonnet[1m]","name":"Sonnet (1M context)","description":"Sonnet 4.6 with 1M context · Billed as extra usage · $3/$15 per Mtok"},{"modelId":"haiku","name":"Haiku","description":"Haiku 4.5 · Fastest for quick answers"}],"currentModelId":"default"},"modes":{"currentModeId":"default","availableModes":[{"id":"default","name":"Default","description":"Standard behavior, prompts for dangerous operations"},{"id":"acceptEdits","name":"Accept Edits","description":"Auto-accept file edit operations"},{"id":"plan","name":"Plan Mode","description":"Planning mode, no actual tool execution"},{"id":"dontAsk","name":"Don't Ask","description":"Don't prompt for permissions, deny if not pre-approved"},{"id":"bypassPermissions","name":"Bypass Permissions","description":"Bypass all permission checks"}]},"configOptions":[{"id":"mode","name":"Mode","description":"Session permission mode","category":"mode","type":"select","currentValue":"default","options":[{"value":"default","name":"Default","description":"Standard behavior, prompts for dangerous operations"},{"value":"acceptEdits","name":"Accept Edits","description":"Auto-accept file edit operations"},{"value":"plan","name":"Plan Mode","description":"Planning mode, no actual tool execution"},{"value":"dontAsk","name":"Don't Ask","description":"Don't prompt for permissions, deny if not pre-approved"},{"value":"bypassPermissions","name":"Bypass Permissions","description":"Bypass all permission checks"}]},{"id":"model","name":"Model","description":"AI model to use","category":"model","type":"select","currentValue":"default","options":[{"value":"default","name":"Default (recommended)","description":"Opus 4.6 with 1M context · Most capable for complex work"},{"value":"sonnet","name":"Sonnet","description":"Sonnet 4.6 · Best for everyday tasks"},{"value":"sonnet[1m]","name":"Sonnet (1M context)","description":"Sonnet 4.6 with 1M context · Billed as extra usage · $3/$15 per Mtok"},{"value":"haiku","name":"Haiku","description":"Haiku 4.5 · Fastest for quick answers"}]}]}}
← {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","update":{"sessionUpdate":"available_commands_update","availableCommands":[]}}}
→ {"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":true,"writeTextFile":true},"terminal":false,"auth":{"terminal":false}},"clientInfo":{"name":"codex","title":"Codex CLI","version":"0.0.0"}},"id":"963d59ae-5561-488a-b1a4-6e0fcfb77905"}
→ {"jsonrpc":"2.0","method":"session/prompt","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","prompt":[{"type":"text","text":"I'm testing something. Just run a foreground sleep 30 task, then say 'all done!'"}]},"id":"c08f997e-8794-4e83-aa41-4c5ae3d3be94"}
← {"jsonrpc":"2.0","id":"963d59ae-5561-488a-b1a4-6e0fcfb77905","result":{"protocolVersion":1,"agentCapabilities":{"_meta":{"claudeCode":{"promptQueueing":true}},"promptCapabilities":{"image":true,"embeddedContext":true},"mcpCapabilities":{"http":true,"sse":true},"loadSession":true,"sessionCapabilities":{"fork":{},"list":{},"resume":{},"close":{}}},"agentInfo":{"name":"@zed-industries/claude-agent-acp","title":"Claude Agent","version":"0.23.1"},"authMethods":[]}}
← {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":""}}}}
← {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":"The user wants me to run a `sleep 30` command in the foreground and then say \"all done!"}}}}
← {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":"\"."}}}}
← {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","update":{"_meta":{"claudeCode":{"toolName":"Bash"}},"toolCallId":"toolu_01TbtohBfxwZB1ZdC5XzaitP","sessionUpdate":"tool_call","rawInput":{},"status":"pending","title":"Terminal","kind":"execute","content":[]}}}
← {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","update":{"_meta":{"claudeCode":{"toolName":"Bash"}},"toolCallId":"toolu_01TbtohBfxwZB1ZdC5XzaitP","sessionUpdate":"tool_call_update","rawInput":{"command":"sleep 30","description":"Sleep for 30 seconds"},"title":"sleep 30","kind":"execute","content":[{"type":"content","content":{"type":"text","text":"Sleep for 30 seconds"}}]}}}
→ {"jsonrpc":"2.0","method":"session/cancel","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf"}}
← {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","update":{"sessionUpdate":"usage_update","used":24422,"size":1000000,"cost":{"amount":0.15535875,"currency":"USD"}}}}
← {"jsonrpc":"2.0","id":"c08f997e-8794-4e83-aa41-4c5ae3d3be94","result":{"stopReason":"cancelled"}}
→ {"jsonrpc":"2.0","method":"session/prompt","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","prompt":[{"type":"text","text":"what have you finished?"}]},"id":"daa0362c-edd8-47b7-ab22-091bc10a2040"}
← {"jsonrpc":"2.0","id":"daa0362c-edd8-47b7-ab22-091bc10a2040","result":{"stopReason":"end_turn","usage":{"inputTokens":0,"outputTokens":0,"cachedReadTokens":0,"cachedWriteTokens":0,"totalTokens":0}}}
→ {"jsonrpc":"2.0","method":"session/prompt","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","prompt":[{"type":"text","text":"."}]},"id":"39d50f73-65bf-4eee-9bc9-0d05527b376e"}
← {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":""}}}}
← {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"Nothing"}}}}
← {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" — I hadn't started any work yet. You asked me to run"}}}}
← {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" `sleep 30`, but the command was rejected before it executed"}}}}
← {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"."}}}}
← {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"9cbea40d-85a1-4b71-8fc9-584be559bbbf","update":{"sessionUpdate":"usage_update","used":24602,"size":1000000,"cost":{"amount":0.169686,"currency":"USD"}}}}
← {"jsonrpc":"2.0","id":"39d50f73-65bf-4eee-9bc9-0d05527b376e","result":{"stopReason":"end_turn","usage":{"inputTokens":3,"outputTokens":32,"cachedReadTokens":24387,"cachedWriteTokens":211,"totalTokens":24633}}}

However, this issue doesn't repro with Zed. Not sure if it actually is intended behavior somehow, or maybe Zed just has the same edge case fix that I made in Nori.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions