pingdotgg / pingdotgg/t3code

[Bug]: Claude thread stays on Working forever after /compact — session stuck at running with no active turn

Open
#7,589 0 comments 0 reactions 0 assignees View on GitHub

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.
Area

apps/server

Steps to reproduce
  1. Run a Claude (claudeAgent) thread long enough that compaction takes a while — on a 1M-window Opus session it takes over a minute.
  2. With the agent idle (not working), send /compact.
  3. Wait for the compaction to finish and the "Context compacted" row to appear.
Expected behavior

The thread settles once compaction is done.

Actual behavior

The thread stays on Working permanently, with a live "Working for Xm Ys" timer, and never settles. There is no way to tell whether compaction is still running or finished long ago.

The projected session ends up in a state that nothing can leave:

status: "running"   active_turn_id: null

Only a turn can move a session out of running, and there is no turn.

Root cause

ClaudeAdapter reports transient CLI heartbeats to orchestration as session.state.changed events carrying a busy state, with no check that a turn owns them.

apps/server/src/provider/Layers/ClaudeAdapter.ts — the status case maps every non-compacting status to running:

state: message.status === "compacting" ? "waiting" : "running",

SDKStatus is 'compacting' | 'requesting' | null, so the status: null the CLI emits when compaction finishes arrives as running. The api_retry case emits state: "running" unconditionally in the same way.

In the shared projector only a turn moves a session out of that state, so a heartbeat that arrives with no turn to attribute it to is unclearable. Compaction routinely outlives the turn that requested it, which is how the post-compaction status lands on a thread whose turn already completed.

Impact

Major degradation or frequent failure

Version or commit

main @ f2d5fc91e (server 0.0.33)

Environment

Server on Linux (Ubuntu, Node v24.19.0), viewed from the macOS desktop app. Provider claudeAgent, Claude Opus 5, 1M context, full-access runtime.

Logs or stack traces
# orchestration_events for the affected thread, in order

11282  19:01:05.198  thread.message-sent          "/compact"
11283  19:01:05.198  thread.turn-start-requested
11284  19:01:05.198  thread.session-set           status=starting  activeTurnId=null
11288  19:01:06.806  thread.session-set           status=starting  activeTurnId=null
11291  19:01:06.845  thread.session-set           status=running   activeTurnId=a24a3f6d-...
11293  19:01:10.639  thread.session-set           status=ready     activeTurnId=null      <- turn completed
11294  19:01:10.640  thread.session-set           status=running   activeTurnId=null      <- no turn to clear it
11297  19:02:55.471  thread.session-set           status=running   activeTurnId=null      <- compaction ends
11298  19:02:55.507  thread.activity-appended     kind=context-window.updated
11299  19:02:55.507  thread.activity-appended     kind=context-compaction
11300  19:02:56.314  thread.activity-appended     kind=context-window.updated
# nothing after this

# projection_thread_sessions, hours later
status=running  active_turn_id=null  updated_at=2026-08-19T19:02:55.471Z

# projection_threads
settled_at=NULL

For reference, the message sequence a /compact produces, captured from the Claude CLI directly:

system/status          status="compacting"
system/status          status=null  compact_result=success
system/init
system/compact_boundary  pre_tokens=28742 post_tokens=2196 duration_ms=11192
user                   (continuation summary)
user                   <local-command-stdout>Compacted </local-command-stdout>
result                 subtype=success  num_turns=0
Workaround

Send any message to the thread. The new turn completes and forces the session back to ready.

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

Start in apps/server/src/provider/Layers/ClaudeAdapter.ts, especially the status and api_retry cases, then trace how the shared projector handles session.state.changed events. Reproduce the /compact sequence and inspect orchestration_events for a session with status running and activeTurnId null. Done means compaction leaves the thread settled with no active turn and the existing context-compaction activity still appears.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.