Before submitting
Area
apps/server
Steps to reproduce
This is intermittent. It is timing-dependent rather than deterministic, and affects roughly 1 in 6 Claude turns that edit files.
- Use the Claude provider with Claude Code CLI 2.1.280 or later.
- Send prompts that edit files and end with a short final reply.
- After each turn, check where the inline "changed files" card renders in the chat timeline.
Expected behavior
The card renders under the turn's final assistant message.
Actual behavior
The card is either missing entirely, or it renders under an earlier assistant message from the same turn. The diff panel and sidebar still show the turn's changes correctly.
The stored checkpoint's assistantMessageId is one of:
- an earlier assistant message from the same turn, so the card renders mid-turn, or
- the fallback
assistant:<turnId>, which matches no message, so no card renders.
Cause (line numbers as of d2c9281b81):
ProviderRuntimeIngestion and CheckpointReactor consume providerService.streamEvents on independent workers (ProviderRuntimeIngestion.ts:2711, CheckpointReactor.ts:1040). Nothing orders them relative to each other.
- On
turn.completed, the reactor reads the thread projection and picks the latest assistant message it can see for the turn. It falls back to assistant:<turnId> if there is none (CheckpointReactor.ts:335-340).
- Claude Code CLI sends
result (which becomes turn.completed) right after the final assistant message. If ingestion hasn't committed that message yet, the reactor attaches the checkpoint to the previous message, or to the fallback id.
- The client keys the card strictly by
assistantMessageId, with no fallback (apps/web/src/components/chat/MessagesTimeline.logic.ts:982, :1405).
Why it started recently: Claude Code 2.1.280 shortened the gap between its final assistant message and result. The median gap went from about 40 ms on 2.1.278 to about 13 ms, and 15–29% of turns now have a gap under 5 ms, down from almost none. That gap was ingestion's whole head start. The mismatch rate tracks it closely (see Logs).
Possible fixes:
- Client: attach the card to the last assistant message of the checkpoint's
turnId. This also repairs checkpoints already stored with a stale id, and applies to the web and mobile timelines alike.
- Server: have the reactor wait for ingestion to process the turn's events before picking the message, or update the checkpoint's
assistantMessageId when the final message lands. This only fixes new turns.
Impact
Minor bug or occasional failure
Version or commit
main @ d7819c1 (plus unrelated fork changes; the code involved is identical on main @ d2c9281)
Environment
macOS 26.6.2, T3 Code desktop (dev build), Claude provider with Claude Opus 5.5 (medium effort), Claude Code CLI 2.1.280–2.1.284 (affected) and 2.1.263–2.1.278 (not affected)
Logs or stack traces
# Measured over ~330 Claude turns on one machine, using the provider native event logs and the projection DB.
# Gap between the CLI's final `assistant` message and its `result`, by CLI version
version turns median gap<5ms
2.1.278 66 39.5ms 0%
2.1.280 68 13.5ms 15%
2.1.281 27 12.0ms 19%
2.1.283 38 11.5ms 29%
2.1.284 34 21.5ms 21%
(2.1.263-2.1.275: medians 25-46ms)
# Turns whose checkpoint assistantMessageId is not the turn's final assistant message, by that gap
gap turns wrong
<5ms 34 21
5-15ms 59 4
15-30ms 95 2
>=30ms 137 0
# One affected turn: the final message is in the event log before the checkpoint,
# yet the checkpoint is attached to the previous message (bde0c332...)
seq event_type messageId
25409 thread.message-sent assistant:a84d4c5f... (streaming)
25411 thread.message-sent assistant:a84d4c5f... (final)
25415 thread.turn-diff-completed assistantMessageId=assistant:bde0c332...
Screenshots, recordings, or supporting files
No response
Workaround
Open the diff panel manually. The turn's diff is still available there and in the sidebar.
Before submitting
Area
apps/server
Steps to reproduce
This is intermittent. It is timing-dependent rather than deterministic, and affects roughly 1 in 6 Claude turns that edit files.
Expected behavior
The card renders under the turn's final assistant message.
Actual behavior
The card is either missing entirely, or it renders under an earlier assistant message from the same turn. The diff panel and sidebar still show the turn's changes correctly.
The stored checkpoint's
assistantMessageIdis one of:assistant:<turnId>, which matches no message, so no card renders.Cause (line numbers as of
d2c9281b81):ProviderRuntimeIngestionandCheckpointReactorconsumeproviderService.streamEventson independent workers (ProviderRuntimeIngestion.ts:2711,CheckpointReactor.ts:1040). Nothing orders them relative to each other.turn.completed, the reactor reads the thread projection and picks the latest assistant message it can see for the turn. It falls back toassistant:<turnId>if there is none (CheckpointReactor.ts:335-340).result(which becomesturn.completed) right after the finalassistantmessage. If ingestion hasn't committed that message yet, the reactor attaches the checkpoint to the previous message, or to the fallback id.assistantMessageId, with no fallback (apps/web/src/components/chat/MessagesTimeline.logic.ts:982,:1405).Why it started recently: Claude Code 2.1.280 shortened the gap between its final
assistantmessage andresult. The median gap went from about 40 ms on 2.1.278 to about 13 ms, and 15–29% of turns now have a gap under 5 ms, down from almost none. That gap was ingestion's whole head start. The mismatch rate tracks it closely (see Logs).Possible fixes:
turnId. This also repairs checkpoints already stored with a stale id, and applies to the web and mobile timelines alike.assistantMessageIdwhen the final message lands. This only fixes new turns.Impact
Minor bug or occasional failure
Version or commit
main @ d7819c1 (plus unrelated fork changes; the code involved is identical on main @ d2c9281)
Environment
macOS 26.6.2, T3 Code desktop (dev build), Claude provider with Claude Opus 5.5 (medium effort), Claude Code CLI 2.1.280–2.1.284 (affected) and 2.1.263–2.1.278 (not affected)
Logs or stack traces
Screenshots, recordings, or supporting files
No response
Workaround
Open the diff panel manually. The turn's diff is still available there and in the sidebar.