Repository navigation
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
- Start a thread turn so
projection_threads.latest_turn_idand the sessionactive_turn_idboth reference the running turn. - Complete the turn and project
thread.session-setwith statusreadyandactiveTurnId: null. Ensure no laterthread.turn-diff-completedevent is projected, or let the terminal session event arrive after the diff event. - Read the thread through
ProjectionSnapshotQuery.getThreadShellById.
The persistence path is deterministic:
applyThreadTurnsProjectioncorrectly settles the running turn.applyThreadsProjectionthen writesevent.payload.session.activeTurnIdintoprojection_threads.latest_turn_id.- Terminal session states carry
activeTurnId: null, so the historical latest-turn pointer is erased. getLatestTurnRowByThreadjoins through that pointer and consequently returns no latest turn.
This became much easier to trigger after commit 501ce27, which correctly clears stale session activeTurnId for generic inactive session.state.changed events. The projection currently conflates the active session turn with the historical latest thread turn.
Expected behavior
An inactive session should have activeTurnId: null, while projection_threads.latest_turn_id should continue referencing the completed/interrupted/error latest turn. Shell snapshots should retain latestTurn and its completedAt.
Actual behavior
projection_threads.latest_turn_id becomes null. Shell snapshots lose latestTurn, so clients lose completion/unread state. The behavior appears intermittent because a later thread.turn-diff-completed event writes the turn ID back; whichever event is projected last wins.
A minimal fix appears to be preserving the existing pointer when the session has no active turn:
latestTurnId:
event.payload.session.activeTurnId ?? existingRow.value.latestTurnIdThis keeps session activity and historical thread state separate. A regression test should project running -> ready with no later checkpoint event and assert that getThreadShellById(...).latestTurn remains completed.
Impact
Minor bug or occasional failure
Version or commit
main @ 9d9208c; regression trigger 501ce27
Environment
macOS desktop; provider-independent orchestration projection path
Logs or stack traces
No exception is emitted; persisted projection state is internally inconsistent.Workaround
A later thread.turn-diff-completed event may restore the pointer, but this is timing-dependent and unavailable for turns without a later checkpoint update.
Activity
t3dotgg commentedon Aug 27, 2026
Thanks for the detailed report. We believe this is fixed by PR #5553.
PR #5553 landed the exact null-coalescing change requested by this report. A ready session with activeTurnId=null now preserves the historical latest-turn pointer. Its lifecycle test projects the terminal session event without a later checkpoint and confirms the pointer remains linked. The originally linked PR being closed for orchestration V2 does not negate this later V1 fix.
I'm closing this as fixed as part of an automated pass on all open issues. If this still happens in a current build that includes PR #5553, please reply with the T3 Code version and fresh logs or reproduction details, and we can reopen it.