[Bug]: /compact as the first message in a forked thread fails with "Cannot compact an empty thread"#24160

opened1d agobyAardaaltinors

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. Have a Claude thread with several user/assistant turns.
  2. Fork it from one of its runs (thread payload ends up with forkedFrom: { type: "run", threadId: <parent>, runId: <parent run> }).
  3. In the new fork, send /compact as the very first message.

Expected behavior

The fork inherits the parent's conversation, so /compact should compact that inherited context, the same as /compact on the parent.

Actual behavior

The run fails immediately without reaching the provider:

Cannot compact an empty thread Start a conversation before compacting this thread.

If the fork already has at least one non-/compact user message of its own, /compact works. Only a fork whose first message is /compact hits this.

Cause

ProviderTurnStartService short-circuits /compact when !projection.hasConversation:

https://github.com/pingdotgg/t3code/blob/66ee628e0875b2b735b199f42e6151e347833637/apps/server/src/orchestration-v2/ProviderTurnStartService.ts#L390-L391

hasConversation only counts user messages whose thread_id is the current thread:

https://github.com/pingdotgg/t3code/blob/66ee628e0875b2b735b199f42e6151e347833637/apps/server/src/orchestration-v2/ProjectionStore.ts#L4259-L4266

A fork does not copy the parent's messages. It only references them through thread.forkedFrom, and its provider thread is still pending (nativeThreadRef: null, status: "not_loaded", forkedFrom.providerThreadId set) until the first real run forks the native session. So for a fresh fork hasConversation is false even though the fork has a full inherited history.

State from an affected install (statev2.sqlite):

threadown user messages/compact result
parent7—
fork (run fork of the parent)1 (/compact only)"Cannot compact an empty thread"
another fork with 4 own messages before /compact10compacted normally

Possible fix: treat the thread as having a conversation when it has forkedFrom (or when its pending provider thread has forkedFrom.providerThreadId), or include the inherited parent messages up to the fork point in the hasConversation check. The in-memory variant at ProjectionStore.ts#L7252 has the same shape.

Impact

Minor bug or occasional failure

Version or commit

v0.1.2 (installed runtime). The same code is on main @ 66ee628.

Environment

T3 server v0.1.2 on macOS (Mac mini), desktop GUI client on another Mac connected remotely. Provider: Claude Agent (claude-opus-5-5).

Workaround

Send any normal message in the fork first so it gets its own user message and native session, then run /compact (untested beyond the observation in the table above).