Skip to content

fix(server): send messages queued during context compaction - #10096

Open
f4llenz wants to merge 5 commits into
pingdotgg:mainfrom
f4llenz:t3code/restore-compaction-message-queue
Open

fix(server): send messages queued during context compaction#10096
f4llenz wants to merge 5 commits into
pingdotgg:mainfrom
f4llenz:t3code/restore-compaction-message-queue

Conversation

@f4llenz

@f4llenz f4llenz commented Sep 5, 2026

Copy link
Copy Markdown

What Changed

Fixes a regression from #9293: sending a message while /compact is running failed with "Wait for context compaction to finish before sending another message." and the message was dropped.

The send now waits for compaction to restore the session, then goes to the provider. Several messages sent during one compaction reach the provider one at a time, in the order they were sent. If the thread stops or errors before compaction settles, the queued send fails with "The thread stopped before this message could be sent." instead of hanging.

In ProviderCommandReactor, the per-thread compaction marker is now a Deferred latch instead of a Set entry. A turn requested during compaction, or while an earlier queued send is still draining, forks a fiber that waits on the previous queued send, then on the latch, re-resolves the thread shell, and starts the turn. There is no in-memory queue to persist or project: the message is already in the event log, so a restart mid-compaction leaves it in the same place as any other in-flight send.

Why

Before #9293 (c5ba51d62), /compact was a message steered into the running provider turn, so a follow-up sent during it queued like any other message. That PR made compaction a server-side command that runs outside any provider turn, and the reactor started rejecting sends while it ran. Users lost the message and had to notice and resend.

#9620 attempted the same fix with a FIFO queue in the reactor plus projection changes. It lost queued sends on restart and had a drain-order race, and was closed. Making the marker itself the latch fixes both with no new state.

Known gap, unchanged from before: the composer's send button becomes Stop while compaction runs, so the follow-up has to be submitted with Enter.

Verification

  • Three new reactor tests: queued sends run once compaction settles, one at a time, in order, with no provider.turn.start.failed; a turn arriving after the latch settles still queues behind a draining send; and a queued send fails when the thread stops before compaction finishes. The full ProviderCommandReactor.test.ts suite passes (63 tests).
  • Server typecheck, scoped lint, and formatting pass.
  • Verified in a local web client on a Claude Haiku 4.5 thread with cloned data. Server-only change; web, desktop, and mobile are unaffected beyond no longer seeing the error.

UI Changes

Before. /compact then a follow-up. The follow-up is rejected and never answered, even after "Compacted context" lands.

before.mp4

After. Same sequence. The follow-up waits, compaction lands, then it is sent and answered.

after.mp4

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Claude Fable 5.1 in T3 Code via the Claude Code harness.

Note

Queue thread.turn.start requests during context compaction in ProviderCommandReactor

Previously, turn requests arriving while a thread was undergoing context compaction were failed instead of sent. The reactor now uses deferred compaction latches and per-thread queued-turn tails so that incoming turns wait for compaction to settle, then send in FIFO submission order.

  • Turns arriving while an earlier queued send is still draining wait behind that send before starting.
  • If the thread is stopped or errored before a queued turn begins, the queued turn is recorded as a turn-start failure instead of being sent.
  • Compaction cleanup now settles latches rather than deleting set entries; compaction admission checks and session-stop handling use the latch map.
  • Risk: queued turns check thread state at send time in ProviderCommandReactor.make; verify that stopped-thread failure messages match expectations for errored sessions, as the failure path now applies to both stopped and errored states.

Macroscope summarized 4505e07.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 5, 2026
Comment thread apps/server/src/orchestration/Layers/ProviderCommandReactor.ts Outdated
@f4llenz
f4llenz marked this pull request as ready for review September 5, 2026 11:48
@f4llenz
f4llenz force-pushed the t3code/restore-compaction-message-queue branch from 6bdc009 to 6831075 Compare September 5, 2026 11:49
@macroscopeapp

macroscopeapp Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This focused server fix introduces per-thread asynchronous latching and FIFO coordination that changes when existing user messages reach the provider. Although targeted tests cover the main scenarios and no schema or infrastructure changes are involved, the runtime concurrency behavior is non-trivial and warrants human review.

No code changes detected at 01720d5. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

@f4llenz
f4llenz force-pushed the t3code/restore-compaction-message-queue branch 6 times, most recently from 2f61f32 to 6fc36bf Compare September 5, 2026 19:02
@cursor

cursor Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

Since pingdotgg#9293 turned /compact into a server-side operation, a message sent
while a thread compacts was rejected with a turn-start failure and the
client dropped it. Messages sent during a running turn steer into that
turn, but compaction runs outside any turn, so there was nothing to
steer into.

The reactor now keeps a per-thread latch for the in-flight compaction
instead of a bare flag. A turn start that arrives while the latch is
held waits for it, then sends through the normal path once the session
is restored. If the thread stopped or errored meanwhile, the message
gets a turn-start failure so the client restores the draft. The message
was already persisted before the reactor saw it, so nothing new needs to
survive a restart.
Waiters woken by the same latch ran as independent fibers, so two
messages queued during compaction could reach the provider out of
order. Each queued send now waits for the one queued before it and
awaits its own provider call before releasing the next, so the thread
sees them in the order they were sent.
A turn requested after the compaction latch settles found no latch to wait
on, so it took the fast path and could overtake a queued send that was
still draining.
@f4llenz
f4llenz force-pushed the t3code/restore-compaction-message-queue branch from 6fc36bf to 4505e07 Compare September 5, 2026 19:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant