You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: Queued messages that run after newer steers never appear in the thread #16987
I searched existing issues and did not find a duplicate.
I included enough detail to reproduce or investigate the problem.
Area
apps/web
Steps to reproduce
Open a thread long enough that the client loads a bounded snapshot (hasMoreHistory: true).
While a run is busy, queue one or more follow-ups. Each gets its run ordinal when it is queued.
Before the queue drains, send messages that start right away (stop, steer, or a new turn after an interrupt). These get higher ordinals and render normally.
Let the queued runs drain. They start and complete, and the agent replies.
Expected behavior
The queued user messages and their replies appear in the open thread as they run.
Actual behavior
They never appear. The thread looks like it stopped at the last message sent directly, so the queued input looks swallowed. Runs that start afterwards render normally. Nothing is lost on the server: the queued runs complete, and their user_message and assistant_message items are in orchestration_v2_projection_turn_items.
In the affected thread, each queued run had a requestedAt 15 to 25 minutes before its startedAt. The directly sent runs between them had higher ordinals but started first.
Impact
Major degradation or frequent failure
Version or commit
0.0.46-nightly.20261007.2774 (server and desktop)
Environment
Self-hosted t3 serve on Linux arm64 with the Claude Code provider. Seen in the desktop app on a separate Linux desktop and on a Mac.
Logs or stack traces
No errors. Provider events show `command_lifecycle` queued → started → completed and `result` / `success`for each affected run.
Screenshots, recordings, or supporting files
No response
Workaround
Reloading does not help in clients that had the thread open when the queued runs drained. They restore the thread from their local cache and resume with afterSequence, so the missing rows stay missing.
A fresh snapshot from the server (GET /api/orchestration/threads/:id/bounded) does include them. Because rows are ordered by ordinal, they appear above directly sent turns that actually ran earlier.
Deleting the thread's entry from the thread store in the t3code:connection-runtime IndexedDB database, then reloading, brings the turns back.
Thanks for the detailed report. I can confirm this in code on main (3143335), though I haven't reproduced it in a running app. The queued run's items are dropped on the client by the partial-timeline "older history" guard, because their ordinal comes from the run's queue-time ordinal.
Mechanism
The ordinal is fixed when the message is queued. A queued follow-up gets nextRunOrdinal(projection) (runs.length + 1) at queue time (Orchestrator.ts#L4850-L4851, #L347-L349). Its user_message turn item is only created when the run starts, but with ordinal: queuedRun.ordinal * 100 (#L1584-L1623). The server then normalizes every run-scoped turn-item.updated into that run's band, runOrdinal * 1_000_000 + n (EventSink.ts#L309-L330, TurnItemPositionStore.ts#L58-L71). A steer or new turn sent later gets a higher run ordinal, so all of its items sort above the still-queued run's.
The client keeps a high-water mark on bounded windows. When hasMoreHistory is true or a history cursor is open, the client applies events with partialTimeline: true and latestLocalTurnOrdinal, and raises that mark on every applied turn-item.updated (threads.ts#L501-L526). The mark starts as the max ordinal across the full projection, taken from the snapshot (threadHistoryPaging.ts#L384-L394).
The guard drops anything at or below the mark.shouldDropMissingPartialTurnItem drops any item that isn't already in the window and has ordinal <= latestLocalTurnOrdinal, on the assumption that it's unloaded history (orchestrationV2Projection.ts#L89-L106). The turn-item.updated reducer then returns the projection unchanged (#L251-L258). Once a directly sent run's items have raised the mark, every item of a lower-ordinal queued run that starts later, its user message and its reply included, falls under it and is silently dropped.
Reload doesn't recover them. The resume cursor still advances past the dropped events (threads.ts#L446-L468). The persisted snapshot stores that sequence (#L556-L566), and on reload the cached entry seeds lastSequence (#L221-L225), which is sent as afterSequence (#L920). The cache is the thread store in t3code:connection-runtime (storage.ts#L41-L45), so the dropped items are never replayed.
A fresh snapshot shows them out of order. The server reads turn items ORDER BY ordinal ASC (ProjectionStore.ts#L2666-L2675), so the queued runs land above directly sent turns that actually ran earlier. That follows from the queue-time ordinal and matches what you saw.
Fix direction (not yet decided)
Client: don't treat an item as unloaded history only because its ordinal is at or below the mark when its run wasn't running at snapshot time. For example, keep a missing item whose runId belongs to a run that started after the snapshot, or that was still queued in the client's projection, and insert it by ordinal as usual. That should stop the rows from vanishing. A test with a queued run N draining after run N+1's items would cover it.
Server ordering: the out-of-order placement after a fresh load is a separate question, whether a queued run's items should sort by when they started rather than when they were queued. That would mean allocating the run's position or band at start. It's a bigger change and a design call.
Existing caches: entries already persisted with the dropped items need a one-time refresh, for example by invalidating cached bounded thread snapshots or bumping the cache version, or affected threads will stay incomplete after the fix.
Workaround: what you found works. Delete the thread's entry from the thread store in the t3code:connection-runtime IndexedDB, then reload, and the client fetches a fresh bounded snapshot.
Related
fix(client): late answers no longer vanish when only part of a thread is loaded #16437 (open) also changes shouldDropMissingPartialTurnItem. It keeps an item when the window already shows an earlier item of the same run. That doesn't cover this case, because a queued run that starts late has no rows in the window yet: its first item, the user message, is the one that gets dropped. The two fixes touch the same guard and should be coordinated.
Also seen with the Grok provider on 0.0.46-nightly.20261008.2813 (desktop app on Linux x64 plus the Android app). Both clients showed the same thing, so it isn't one stale client cache.
In one thread, follow-ups queued while a run was held open (Grok background subagents, see #15489) ended up like this:
Queued scheduled-task posts (runs 21 and 22, requested 09:30 and 09:35) were applied as steers into the older run 17 at 09:32:05 and 09:50:04, so their messages appear under run 17's later attempts.
Runs 23 and 24 (requested 09:40 and 09:45) went cancelled at 09:50:05–06 without ever starting a provider turn. Their user messages still exist at their original timestamps.
Run 25 (requested 09:50:34) started at 09:51:58, after the held run finally released.
The user saw history "rewrite": steered messages showed up out of order, and some earlier user and agent messages vanished from the timeline. The vanished agent messages are mostly the superseded-attempt folds from #15489.
Posted from a t3 triage session by Claude Code (Opus 5.5).
Activity
juliusmarminge commentedon Oct 7, 2026
Note
Grok responding on behalf of Julius.
Thanks for the detailed report. I can confirm this in code on main (
3143335), though I haven't reproduced it in a running app. The queued run's items are dropped on the client by the partial-timeline "older history" guard, because their ordinal comes from the run's queue-time ordinal.Mechanism
nextRunOrdinal(projection)(runs.length + 1) at queue time (Orchestrator.ts#L4850-L4851, #L347-L349). Itsuser_messageturn item is only created when the run starts, but withordinal: queuedRun.ordinal * 100(#L1584-L1623). The server then normalizes every run-scopedturn-item.updatedinto that run's band,runOrdinal * 1_000_000 + n(EventSink.ts#L309-L330, TurnItemPositionStore.ts#L58-L71). A steer or new turn sent later gets a higher run ordinal, so all of its items sort above the still-queued run's.hasMoreHistoryis true or a history cursor is open, the client applies events withpartialTimeline: trueandlatestLocalTurnOrdinal, and raises that mark on every appliedturn-item.updated(threads.ts#L501-L526). The mark starts as the max ordinal across the full projection, taken from the snapshot (threadHistoryPaging.ts#L384-L394).shouldDropMissingPartialTurnItemdrops any item that isn't already in the window and hasordinal <= latestLocalTurnOrdinal, on the assumption that it's unloaded history (orchestrationV2Projection.ts#L89-L106). Theturn-item.updatedreducer then returns the projection unchanged (#L251-L258). Once a directly sent run's items have raised the mark, every item of a lower-ordinal queued run that starts later, its user message and its reply included, falls under it and is silently dropped.lastSequence(#L221-L225), which is sent asafterSequence(#L920). The cache is thethreadstore int3code:connection-runtime(storage.ts#L41-L45), so the dropped items are never replayed.ORDER BY ordinal ASC(ProjectionStore.ts#L2666-L2675), so the queued runs land above directly sent turns that actually ran earlier. That follows from the queue-time ordinal and matches what you saw.Fix direction (not yet decided)
runIdbelongs to a run that started after the snapshot, or that was still queued in the client's projection, and insert it by ordinal as usual. That should stop the rows from vanishing. A test with a queued run N draining after run N+1's items would cover it.Workaround: what you found works. Delete the thread's entry from the
threadstore in thet3code:connection-runtimeIndexedDB, then reload, and the client fetches a fresh bounded snapshot.Related
shouldDropMissingPartialTurnItem. It keeps an item when the window already shows an earlier item of the same run. That doesn't cover this case, because a queued run that starts late has no rows in the window yet: its first item, the user message, is the one that gets dropped. The two fixes touch the same guard and should be coordinated.session-logic.ts.Mkassabov commentedon Oct 8, 2026
Also seen with the Grok provider on
0.0.46-nightly.20261008.2813(desktop app on Linux x64 plus the Android app). Both clients showed the same thing, so it isn't one stale client cache.In one thread, follow-ups queued while a run was held open (Grok background subagents, see #15489) ended up like this:
cancelledat 09:50:05–06 without ever starting a provider turn. Their user messages still exist at their original timestamps.The user saw history "rewrite": steered messages showed up out of order, and some earlier user and agent messages vanished from the timeline. The vanished agent messages are mostly the superseded-attempt folds from #15489.
Posted from a
t3 triagesession by Claude Code (Opus 5.5).