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]: Usage-limit auto-resume resumes the session but leaves the queue paused #26037
When T3 Code automatically resumes a session after its usage limit resets, the message queue remains paused. The session continues, but queued follow-ups still need a separate manual resume. This leaves unattended work only partly resumed.
Area
apps/server (suspected; queue and usage-limit recovery behavior)
Steps to reproduce
Reported sequence:
Have queued messages in a thread.
The session reaches its usage limit.
T3 automatically resumes the session after the limit resets.
The queue remains paused instead of continuing afterward.
This sequence reflects the observed behavior. It has not been independently reproduced in a controlled test, and the original cause of the queue hold has not been confirmed.
Expected behavior
When queued messages are held because of a usage limit, successful automatic recovery should allow them to continue. A queue explicitly paused by the user should retain that pause.
Actual behavior
The session resumes automatically, but the queue stays paused and requires a separate manual resume.
Impact
Unattended queued work remains blocked after the session resumes. Frequency and affected-provider scope have not been established.
Related work
PR #21473 makes a manually sent message release a held queue, but explicitly excludes server continuations after usage limits. That may explain this behavior; it does not establish the cause in this incident.
Issue #18805 discusses sessions continuing while older queued messages remain held. This report concerns automatic recovery after a usage-limit reset.
Is preserving the queue hold intentional when the hold came solely from the usage limit?
Version or commit
The current server at the time of drafting reports T3 Code 0.1.3. The incident version has not been confirmed separately.
Environment
Current server environment: macOS arm64. The affected provider/model and whether Stop or a restart contributed to the hold remain unconfirmed.
Logs or stack traces
No incident logs or controlled reproduction are attached.
Workaround
Resume the queue separately using Resume / Resume queue. This recovery control is documented in the related work; it has not been independently tested against this incident.
I spent some time tracing this on main (5808469). Here's what I found, plus a question about intended behavior before anyone writes a fix.
A plain usage limit doesn't hold the queue
When a run fails with usage_limit and the session has no newer lastError, startNextQueuedRun returns early on usageLimitHoldsQueue(...) (Orchestrator.ts:1508). Queued runs simply wait, with no queueHeld. The auto-resume continuation then runs, and when it completes, usageLimitBlockedRun no longer matches (the latest executed run is now the completed continuation), so the queue starts on its own. So the sequence in the report shouldn't reproduce by itself.
Likely trigger: a server restart while waiting for the reset
Usage-limit waits are long: hours, often overnight. If the server restarts during that wait (desktop update, app relaunch, crash), ProviderRuntimeRecoveryService holds every queued run (ProviderRuntimeRecoveryService.ts:322-330, { ...run, queueHeld: true }). At the reset, UsageLimitRecoveryWorker dispatches the continuation, but a server-created continuation deliberately doesn't release holds (#21473: "server continuations after a usage limit or a restart … aren't the user coming back"). The session resumes and the queue stays held, which matches this report exactly.
A second, rarer path: the hold branch at Orchestrator.ts:~1536 (failureClass !== undefined …) also holds the queue for a usage-limit failure when the session's lastError differs from the failure message, e.g. after the provider session was released with a runtime error.
Question for maintainers
#21473 intentionally keeps holds through server continuations. Is that also intended when auto-resume is armed (the user opted in via "Resume after usage limits" or "Resume at reset") and the hold came from the limit wait or a restart during it, rather than from a Stop?
If the answer is "it should continue", here's a focused proposal:
Record why a run is held. One option is an optional queueHeldByRunId on the run, stamped by the usage-limit hold and by restart recovery with the run that's blocked by the limit.
The usage-limit continuation releases only holds stamped with the run it continues. Stop clears the stamp, so a user's Stop always wins, and legacy unstamped holds stay held.
I have a working prototype with tests for the stamping approach (two limits in a row, a reordered queue, and a Stop before or after the limit). I'd rework it around the restart path and open a PR only if you confirm the direction.
I tried to reproduce this on the current build and could not.
Not seen in a running app: a real usage-limit failure needs first-party Claude/ChatGPT limits, which no VM has. Reproduced instead as a failing test on origin/main 31a108c. The test runs the real OrchestratorV2 + ProjectionStore + ProviderRuntimeRecoveryService layer (apps/server/src/orchestration-v2/runtimeLayer.test.ts, suite 'usage-limit recovery', test 'starts the queue after a scheduled continuation that follows a restart'). Steps: 1) create a thread with a deferred user message and a queued user follow-up; 2) fail the first run with a usage_limit error, resetAt in 1 minute; 3) arm auto-resume through limitRecoveryCommand + autoArmLimitRecovery; 4) recovery.reconcile('startup') to simulate a server restart during the wait; 5) advance the clock 1 minute and dispatch the scheduled continuation from limitRecoveryCommand. It starts ('starting'). 6) complete the continuation and call resumeQueuedRuns. Observed: the queued follow-up stays queueHeld: true and never starts. Control: with step 4 removed, the same test passes and the follow-up starts on its own. So the restart hold is the trigger, and a plain usage limit alone does not reproduce it (this matches the analysis in the issue comment).
Closing as not planned. If it still happens for you, reply with the exact steps and the version you are on and it will be looked at again.
Thanks for writing the test, @juliusmarminge. I think it confirms the bug rather than ruling it out. On current main, the queued follow-up stays queueHeld: true after the scheduled continuation completes, and the control without the restart passes. That's the behavior this issue reports.
The restart isn't an unusual extra step here. A usage-limit wait usually lasts hours or overnight, and a desktop update, an app relaunch or a crash during that wait is enough to trigger it. With auto-resume on, the user isn't around to notice that the queue never resumed.
Could this be reopened? I have a prototype that releases only the holds tied to the run the continuation resumes, so a user's Stop still wins. If you're open to that approach, I can rebase it on main, check it against your test, and open it as a PR.
Activity
rwspatin commentedon Oct 28, 2026
I spent some time tracing this on
main(5808469). Here's what I found, plus a question about intended behavior before anyone writes a fix.A plain usage limit doesn't hold the queue
When a run fails with
usage_limitand the session has no newerlastError,startNextQueuedRunreturns early onusageLimitHoldsQueue(...)(Orchestrator.ts:1508). Queued runs simply wait, with noqueueHeld. The auto-resume continuation then runs, and when it completes,usageLimitBlockedRunno longer matches (the latest executed run is now the completed continuation), so the queue starts on its own. So the sequence in the report shouldn't reproduce by itself.Likely trigger: a server restart while waiting for the reset
Usage-limit waits are long: hours, often overnight. If the server restarts during that wait (desktop update, app relaunch, crash),
ProviderRuntimeRecoveryServiceholds every queued run (ProviderRuntimeRecoveryService.ts:322-330,{ ...run, queueHeld: true }). At the reset,UsageLimitRecoveryWorkerdispatches the continuation, but a server-created continuation deliberately doesn't release holds (#21473: "server continuations after a usage limit or a restart … aren't the user coming back"). The session resumes and the queue stays held, which matches this report exactly.A second, rarer path: the hold branch at
Orchestrator.ts:~1536(failureClass !== undefined …) also holds the queue for a usage-limit failure when the session'slastErrordiffers from the failure message, e.g. after the provider session was released with a runtime error.Question for maintainers
#21473 intentionally keeps holds through server continuations. Is that also intended when auto-resume is armed (the user opted in via "Resume after usage limits" or "Resume at reset") and the hold came from the limit wait or a restart during it, rather than from a Stop?
If the answer is "it should continue", here's a focused proposal:
queueHeldByRunIdon the run, stamped by the usage-limit hold and by restart recovery with the run that's blocked by the limit.I have a working prototype with tests for the stamping approach (two limits in a row, a reordered queue, and a Stop before or after the limit). I'd rework it around the restart path and open a PR only if you confirm the direction.
juliusmarminge commentedon Nov 12, 2026
I tried to reproduce this on the current build and could not.
Not seen in a running app: a real usage-limit failure needs first-party Claude/ChatGPT limits, which no VM has. Reproduced instead as a failing test on origin/main 31a108c. The test runs the real OrchestratorV2 + ProjectionStore + ProviderRuntimeRecoveryService layer (apps/server/src/orchestration-v2/runtimeLayer.test.ts, suite 'usage-limit recovery', test 'starts the queue after a scheduled continuation that follows a restart'). Steps: 1) create a thread with a deferred user message and a queued user follow-up; 2) fail the first run with a usage_limit error, resetAt in 1 minute; 3) arm auto-resume through limitRecoveryCommand + autoArmLimitRecovery; 4) recovery.reconcile('startup') to simulate a server restart during the wait; 5) advance the clock 1 minute and dispatch the scheduled continuation from limitRecoveryCommand. It starts ('starting'). 6) complete the continuation and call resumeQueuedRuns. Observed: the queued follow-up stays queueHeld: true and never starts. Control: with step 4 removed, the same test passes and the follow-up starts on its own. So the restart hold is the trigger, and a plain usage limit alone does not reproduce it (this matches the analysis in the issue comment).
Closing as not planned. If it still happens for you, reply with the exact steps and the version you are on and it will be looked at again.
rwspatin commentedon Nov 12, 2026
Thanks for writing the test, @juliusmarminge. I think it confirms the bug rather than ruling it out. On current main, the queued follow-up stays
queueHeld: trueafter the scheduled continuation completes, and the control without the restart passes. That's the behavior this issue reports.The restart isn't an unusual extra step here. A usage-limit wait usually lasts hours or overnight, and a desktop update, an app relaunch or a crash during that wait is enough to trigger it. With auto-resume on, the user isn't around to notice that the queue never resumed.
Could this be reopened? I have a prototype that releases only the holds tied to the run the continuation resumes, so a user's Stop still wins. If you're open to that approach, I can rebase it on main, check it against your test, and open it as a PR.