[Bug]: after idle session.exited, a resumed claudeAgent thread accepts messages and never starts a turn
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 23k
- Forks
- 5.9k
- Avg merge
- 11h 14m
- Merged PRs (30d)
- 357
Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Closest open issues: #5587 and #4713. Those are interrupt not killing the claudeAgent child, and the session staying running after interrupt. This report is the resume hang. A second thread the same night is a compacting stall, not this repro.
Area
apps/server
Steps to reproduce
Not a guaranteed click path. Idle resume sometimes works. This is what produced the mute thread.
- T3 Code Nightly desktop, local environment, provider
claudeAgent. - Use an existing thread that already has history. Send a short prompt and wait for the reply to finish.
- Leave that thread alone until the session stops on its own. On this build that was about 4 minutes after the last reply.
- Come back later and send a normal prompt on the same thread. Ours went dark after ~95 minutes. A second thread the same night resumed after a similar wait and streamed, so the wait alone is not enough.
- Send a follow-up on the dark thread. Type
/stop. - Open a new thread in the same project and send a prompt there.
If it reproduces, the old thread shows your messages and never replies. The new thread answers.
I only saw this on the desktop client, local environment, claudeAgent. I did not try web, mobile, T3 Connect, a remote/relay environment, or another provider.
Expected behavior
The next prompt on that thread starts a turn and streams a reply.
Actual behavior
The prompt is stored on the thread and no turn starts. On Electric, four user messages from 00:38:38Z to 01:02:41Z all landed with turn_id empty. No assistant text until the session later died.
processTurnStartRequested (ProviderCommandReactor.ts:1071) accepted each thread.turn-start-requested and forked ProviderService.sendTurn (:1182). ensureSessionForThread took the cold-start branch and called startSession with no resume cursor (:730). ProviderService.startSession backfilled the persisted binding (ProviderService.ts:568). ClaudeAdapter.startSession (ClaudeAdapter.ts:3705) emitted session.started / session.configured / session.state.changed at 00:38:39Z, then forked the SDK stream.
ClaudeAdapter.sendTurn (:4303) did not emit turn.started until 01:09:34Z. That emit is after optional query.setModel and query.setPermissionMode, and before Queue.offer. Every request had interactionMode: "default", so setPermissionMode always runs. We did not capture a span proving which await hung. The block could also be resolveRoutableSession / recoverSessionForThread (ProviderService.ts:718).
Session stayed starting with activeTurnId null. No provider.turn.start.failed. At 01:09:34.908Z all four turn.started landed in the same millisecond and the session exited 272ms later. The next prompt at 01:35:39Z started a fresh resume and reached turn.started in ~360ms.
Further prompts do not recover it. /stop is stored as another user message, not a stop. This is not #5587. Electric has no thread.turn-interrupt-requested. interruptTurn (ClaudeAdapter.ts:4413) calls query.interrupt() and does not kill the child.
A new thread in the same project keeps working.
Impact
Major degradation or frequent failure
Version or commit
0.0.34-nightly.20260811.1069
Environment
macOS 26.5.1 (Darwin 25.5.0 arm64), T3 Code Nightly desktop, local environment, Bun 1.3.14, Node 22.23.0, Claude Code 2.1.206, provider claudeAgent, model claude-grok-4.6
Logs or stack traces
Electric `7757a359-0eb5-4b9b-bffe-499d1572d07d`, resume `4d7d8eac-5fbf-455d-9689-91a4ccc13def`:
22:59:52Z turn completed
23:03:59Z session.exited (idle, ~4 min, reason "Session stopped", exitKind graceful)
00:38:38Z user prompt stored, turn_id empty
00:38:39Z session.started / session.configured / session.state.changed
session.started payload carries resume 4d7d8eac-…
then no provider events
00:39:43Z user "Continue" stored, turn_id empty
01:00:36Z user "Where are we with this?" stored, turn_id empty
01:02:41Z user "/stop" stored, turn_id empty
01:09:34Z four turn.started in the same millisecond
01:09:35Z session.exited 272ms later
01:35:39Z next prompt; fresh startSession; turn.started ~360ms later
Orchestration events on that thread, 00:38–01:10:
00:38:38Z thread.message-sent + thread.turn-start-requested accepted
00:38:39Z session.started / session.configured
00:39:43Z same pair, turn_id still empty
01:00:36Z same pair
01:02:41Z /stop stored as the same pair
01:09:34Z first provider turn.started
Detection query (same idea as #4713):
SELECT created_at, role, turn_id
FROM projection_thread_messages
WHERE thread_id = '7757a359-0eb5-4b9b-bffe-499d1572d07d'
AND created_at BETWEEN '2026-08-16T00:38:00Z' AND '2026-08-16T01:03:00Z';
-- four user rows, all turn_id NULL
Projects `97e2a3fa-301a-4888-9cff-831fe611a24d` is a different failure on the same night. Resume did start a turn, then the provider stuck on `status compacting` for 1780s. I am leaving that here as related evidence, not as the repro.
Screenshots, recordings, or supporting files
CleanShot 2026-08-15 at 20.55.56@2x.png
Workaround
Do not keep prompting the dark thread. /stop will not recover it.
If a claude process is still up with that thread's --resume= id, kill that PID only. Do not pattern-match. The next message on the same thread recovered both of these.
If the session is already stopped and there is no child, just send one message. That is how Electric recovered.
A new thread also works.
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
Trace the reported flow through ProviderCommandReactor.ts, ProviderService.ts, and ClaudeAdapter.ts, starting at processTurnStartRequested and the cited ensureSessionForThread/startSession/sendTurn paths. Reproduce or instrument an idle claudeAgent resume around setPermissionMode, resolveRoutableSession, and recoverSessionForThread. Done means the next prompt starts a turn and streams a reply rather than leaving messages without turn_id or a response.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend-api-design
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100