[Bug]: Claude thread stays on Working forever after /compact — session stuck at running with no active 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.
Area
apps/server
Steps to reproduce
- Run a Claude (
claudeAgent) thread long enough that compaction takes a while — on a 1M-window Opus session it takes over a minute. - With the agent idle (not working), send
/compact. - 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
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 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