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

[View on GitCafe](https://git.cafe/pingdotgg/t3code/issues/24160)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: completed

Priority: 0

## Description

[Discussion and activity](https://git.cafe/pingdotgg/t3code/issues/24160)

---

### Before submitting

- [x] I searched existing issues and did not find a duplicate.
- [x] 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`):

| thread | own user messages | `/compact` result |
|---|---|---|
| parent | 7 | — |
| fork (run fork of the parent) | 1 (`/compact` only) | "Cannot compact an empty thread" |
| another fork with 4 own messages before `/compact` | 10 | compacted 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).

