pingdotgg / pingdotgg/t3code

[Bug]: after idle session.exited, a resumed claudeAgent thread accepts messages and never starts a turn

Open
#7,155 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug needs-triage
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.

  1. T3 Code Nightly desktop, local environment, provider claudeAgent.
  2. Use an existing thread that already has history. Send a short prompt and wait for the reply to finish.
  3. Leave that thread alone until the session stops on its own. On this build that was about 4 minutes after the last reply.
  4. 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.
  5. Send a follow-up on the dark thread. Type /stop.
  6. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.