Usage-limit auto-resume is dropped when a later notification-triggered run completes on the same thread#32286

opened4d agobyMMoatasemAshmawy

What happened

A thread hit the Claude 5-hour usage limit, and T3 Code set up an auto-resume for the reset time. Nineteen seconds later, a second subagent-failure notification started another run on the same thread. That run "completed" in about 140ms with no output and no error. When the reset time passed, the thread never resumed. It stayed idle until the user noticed by hand.

Diagnosis

UsageLimitRecoveryWorker gets its candidates from ProjectionStore.getLimitRecoveryCandidates. That query joins only the thread's latest executed run (ordered by completed_at) and requires r.status = 'failed' (apps/server/src/orchestration-v2/ProjectionStore.ts:3981-3994 at v0.1.8-nightly.20261105.4218; unchanged on main, around line 4016).

Run 77 failed with usage_limit, and the thread's limitRecovery was set up for it (runId: …ordinal:77, resetAt: 2026-11-07T02:30:00.000Z, autoResume: true). Run 78 was started by a background subagent's failure notification and ended with status completed. Run 78 is now the latest executed run, so the r.status = 'failed' join no longer matches and the thread drops out of the candidates. The armed recovery for run 77 can never fire.

A second, related problem: run 78 started during an active usage-limit window and was recorded as completed instead of failing as usage_limit or waiting until the reset.

Possible fix directions (not tested):

  • While limitRecovery is armed and the reset time is in the future, hold notification-triggered dispatches until the resume.
  • Or, when a run is the latest run but did nothing, still treat the armed run's recovery as valid.

Steps to reproduce

  1. On a Claude thread with auto-resume on (autoResumeLimitedThreads: true), start two background subagents (Agent tool).
  2. Hit the 5-hour usage limit so that both subagents fail with HTTP 429 a few seconds apart.
  3. The first failure notification starts a run, which fails with usage_limit and shows "This turn is paused until the 5-hour limit resets in Nm". Recovery is armed.
  4. The second failure notification starts another run, which ends as completed almost immediately with no output.
  5. Wait past the reset time: the thread is not resumed.

Version

0.1.8-nightly.20261105.4218 (desktop app and local server, macOS). The bug happened on this build. The triage CLI version is the same.

Environment

  • Desktop app with its local server, macOS 27.0.0 arm64, Node v24.22.1
  • Provider: claudeAgent, model claude-opus-5-5
  • Thread created from the mobile app, auto-resume on

Evidence

shell
1- Run 77: `failed`; usage-limit notice at 2026-11-07T02:09:31Z; reset 02:30:00Z.
2- Run 78: `completed`; started 02:09:50.867Z, completed 02:09:51.008Z; triggered by a background subagent's failure notification; empty checkpoint.
3- Thread `limitRecovery` after the reset (still present, never consumed):
4 `{"runId":"run:thread:<id>:ordinal:77","resetAt":"2026-11-07T02:30:00.000Z","autoResume":true,"snooze":false,"requestId":"limit-arm:<id>:run:thread:<id>:ordinal:77:1794018600000"}`
5- No `limit-resume` dispatch was ever made for the thread.

#30144 and #21091 (other usage-limit recovery edge cases involving subagents). Neither covers this case.

Fix applied or workaround

None applied. Workaround: manually send "Continue where you left off." in the thread after the reset.

Filed by

Claude Code (Opus 5.5, claude-opus-5-5) via T3 Code triage.

Synced from GitHub

Additional occurrence on Windows x64, investigated on November 11, 2026.

Environment: official Nightly desktop installation with local server; Claude Agent provider, claude-opus-5-5, native background subagents. autoResumeLimitedThreads is enabled. The inspected server and installed bundle report 0.1.9-nightly.20261110.4465; the exact build running at the original failure was not independently established.

Observed sequence: timestamps below are relative and run labels are arbitrary.

  • Run A failed with failure.class: usage_limit, code api_error_429, and a valid future reset R.
  • The persisted limitRecovery references A, with autoResume: true and the same reset R.
  • Three background notification runs completed approximately 3 seconds, 28 seconds, and 3 minutes 49 seconds after A. They contained notifications, empty checkpoints, and one usage-limit notice; no productive assistant work.
  • No run started at R. Work resumed only after the user manually continued approximately 3 hours 24 minutes later.
  • The saved recovery for A remained present after the missed reset.

Verification: extracted getLimitRecoveryCandidates from the installed server bundle and executed its SQL against an isolated, in-memory copy of this thread’s relevant projection records. Excluded the later manual compaction and continuation to reconstruct the state at R.

The installed query returned no candidate just after R. With only the three notification runs omitted, the same query returned the thread. Its latest-executed-run join requires r.status = 'failed', so the notification completions hide A.

This corroborates the already-armed recovery failure in this issue. It is a persisted-state/query replay, not a fresh end-to-end quota-exhaustion reproduction. No installed code or live database was modified.

Manual continuation worked. No fix was applied.

Prepared by GPT-6.1-Sol through the Codex harness in T3 Code. No t3 triage launcher was run. Thread/project identifiers, names, paths, account details, conversation content, and absolute timestamps are omitted.