fix(server): preserve queued messages after usage limits - #13877
Conversation
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This is a cross-layer runtime behavior change that alters automatic queue dispatch and Stop semantics, persists queue-hold state, and introduces a new manual continuation action across the server, client, and web UI. Because it adds a user-facing workflow and gates provider work based on usage and failure state, it warrants human review. You can add or adjust custom eligibility rules. Learn more. |
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: unavailable · PR result: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
9e3958a to
e32db91
Compare
A provider that just failed will likely fail the next queued message too, so hold the queue for the same provider until the user resumes it. Validation failures and messages queued for another provider still start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A usage limit only blocks queued messages for the limited provider. Queuing a message for a different provider is how users recover, so it now starts automatically and queue.resume accepts it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…usage limit" A queued message is meant to run after the current task finishes. A usage limit stops that task, so every queued message waits for the user, whichever provider it targets. This reverts commit 2740d98. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The usage-limit recovery sweep passed its auto-resume and snooze switches
to SQLite as JS booleans. Node 24.20, which Exarch bundles, refuses them
("Provided value cannot be bound to SQLite parameter 3"), so the sweep
failed every 5 seconds and logged "Scheduler source failed". Node 24.21 and
later accept booleans, which is why upstream's tests never saw it.
Both switches now go through the booleanInt helper the file already uses,
character for character as upstream e32db91 (PR pingdotgg#13877) does, so the
refresh that brings that commit merges cleanly.
The new test runs the real query on in-memory node:sqlite with a guard that
refuses boolean binds on any Node, across all four switch combinations.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A provider usage limit could send queued messages into the same exhausted allowance, while the queue hid the failed turn. After a stop, an empty composer also offered no direct way to continue the interrupted work.
Hold queued runs until the stopped turn continues. Keep the failed run visible in thread summaries, preserve restart-held queues, and offer scheduled recovery at the provider's reset time. On web and desktop, an empty composer now shows a play button for interrupted or limited turns. A manual click starts a guarded continuation ahead of queued messages, and a stale click cannot start a second one. The same button releases a queue held after a server restart.
A Stop from the client also holds the queue, as does a provider failure when the next queued message targets the same provider. Validation failures and messages queued for another provider still start automatically.
Checks: Focused server, composer, and queue tests passed. Server, web, client runtime, contracts, and shared typechecks passed. Targeted lint passed.
Model: GPT-6
Harness: Codex
🤖 Generated with Claude Code