Thread session stuck in `running` after turn interrupt — stop button becomes a no-op
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 23k
- Forks
- 5.9k
- Avg merge
- 11h 14m
- Merged PRs (30d)
- 357
Description
Version: 0.0.29-nightly.20260727.915 · macOS · provider claudeAgent
When a turn is interrupted, the turn projection is finalized correctly but no follow-up thread.session-set event is emitted. The session projection stays running with active_turn_id still pointing at the now-terminal turn.
From that point the thread is unrecoverable from the UI: every stop press emits a thread.turn-interrupt-requested event that is recorded as accepted in orchestration_command_receipts, but the orchestrator has no active turn left to act on, so nothing happens. There is no visible signal that the turn already ended.
Observed timeline
One thread, times UTC:
21:00:35.001—thread.session-set→running, turn06dd4ca921:02:41— turn interrupted: sub-agents emittask.completed/status: stopped,projection_turns.state = interrupted,completed_atset21:02:41onwards — no furtherthread.session-set; session projection frozen atrunning21:04→05:42(next day) — 40+thread.turn-interrupt-requested, allaccepted, no effect
The provider child process stayed alive for 9 hours, in state R, RSS growing from 394 MB to 737 MB.
Detection query
This returns rows only in the broken state:
SELECT s.thread_id, tu.state, tu.completed_at
FROM projection_thread_sessions s
JOIN projection_turns tu ON tu.turn_id = s.active_turn_id
WHERE s.status = 'running'
AND tu.state IN ('interrupted','completed','error')
AND tu.completed_at IS NOT NULL;
Workaround
SIGTERM the provider child process whose --resume matches the thread's resume cursor. The adapter then emits thread.session-set → stopped with lastError: "Claude runtime stream failed.", and the turn is finalized as error. The thread is fully resumable afterwards, with history intact.
Suggested fix
Emit the session transition on the interrupt path itself, rather than relying solely on provider stream termination. A secondary guard — refusing to hold active_turn_id on a turn already in a terminal state — would make the desync self-correcting.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing the turn interrupt path and the session/turn projection updates described in the issue; run the detection query to reproduce or verify the desynchronized state. Confirm that interruption emits a stopped session transition and that a terminal turn cannot remain active. Add coverage for the interrupted, completed, and error states if the relevant tests are located.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend, databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100