Bug Description
When sending a new prompt while the agent is actively running (direct interrupt), the OpenCode session can get stuck in a half-aborted state if the previous session had tools actively executing (e.g., Bash commands, terminal reads).
The bridge's handleStartAgent correctly aborts the old session via POST /session/:id/abort and starts a new one. However, when the previous session has in-flight tool executions, the abort doesn't cleanly terminate them. The new session starts (1 claude_message received) but then goes permanently silent — no more agent output, no error, no exit.
Steps to Reproduce
- Start an agent on a thread (e.g., "start the app again")
- Wait for the agent to begin executing tools (Bash, terminal operations)
- While tools are running, send a new prompt (e.g., "let's start the project on port 5000") via the queued prompt Play button
- The new prompt is sent directly to the bridge (no stop_agent first)
- The bridge aborts the old session and starts a new one
- The new session emits 1
claude_message (init) then goes completely silent
Expected Behavior
The old session should be cleanly terminated (including running tools), and the new session should start and produce output normally.
Actual Behavior
The new session gets stuck after initialization. No claude_message, claude_error, or claude_exit events are emitted. The thread remains in running status indefinitely until the activity timeout fires (5 minutes).
Root Cause Analysis
The OpenCode serve /session/:id/abort endpoint may not fully clean up running tool processes. When the bridge's poll loop detects the aborted session's status change, there's a race with the new session starting:
- Bridge aborts old session (has running Bash/terminal tools)
- 2-second drain delay passes
- Bridge starts new session via
sendPrompt
- Old session's tool processes may still be running in OpenCode
- New session gets stuck (possibly blocked by the old session's resources)
Current Workaround
Use the Stop button first, wait for the thread to reach completed, then send the new prompt. The stop-then-send flow via stop_agent → stopClaude is more aggressive and cleans up properly.
Investigation Notes
- The bridge's
handleStartAgent adds the old session to suppressedAborts before aborting
- A 2-second drain delay was added to let in-flight poll callbacks exit
- The
activeThreads.delete(threadId) stops the old poll loop, but in-flight callbacks may still fire
- The new session's poll loop starts but may conflict with lingering old session state in OpenCode serve
Related Work
This was discovered during the fix for destroyed proxy sandbox recovery (branch fix/proxy-recovery-and-agent-interrupt). The direct interrupt approach was reverted in favor of stop-then-send for reliability.
Bug Description
When sending a new prompt while the agent is actively running (direct interrupt), the OpenCode session can get stuck in a half-aborted state if the previous session had tools actively executing (e.g., Bash commands, terminal reads).
The bridge's
handleStartAgentcorrectly aborts the old session viaPOST /session/:id/abortand starts a new one. However, when the previous session has in-flight tool executions, the abort doesn't cleanly terminate them. The new session starts (1claude_messagereceived) but then goes permanently silent — no more agent output, no error, no exit.Steps to Reproduce
claude_message(init) then goes completely silentExpected Behavior
The old session should be cleanly terminated (including running tools), and the new session should start and produce output normally.
Actual Behavior
The new session gets stuck after initialization. No
claude_message,claude_error, orclaude_exitevents are emitted. The thread remains inrunningstatus indefinitely until the activity timeout fires (5 minutes).Root Cause Analysis
The OpenCode serve
/session/:id/abortendpoint may not fully clean up running tool processes. When the bridge's poll loop detects the aborted session's status change, there's a race with the new session starting:sendPromptCurrent Workaround
Use the Stop button first, wait for the thread to reach
completed, then send the new prompt. The stop-then-send flow viastop_agent→stopClaudeis more aggressive and cleans up properly.Investigation Notes
handleStartAgentadds the old session tosuppressedAbortsbefore abortingactiveThreads.delete(threadId)stops the old poll loop, but in-flight callbacks may still fireRelated Work
This was discovered during the fix for destroyed proxy sandbox recovery (branch
fix/proxy-recovery-and-agent-interrupt). The direct interrupt approach was reverted in favor of stop-then-send for reliability.