# \[Bug\]: Usage-limited Claude thread is never armed for auto-resume when background-task wake runs finish as \`completed\` right after the limit stop · Issue \#16815 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/issues/16815)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: backlog

Priority: 0

## Description

[Discussion and activity](https://git.cafe/pingdotgg/t3code/issues/16815)

---

## Before submitting

- [x] I searched existing issues and did not find a duplicate.
- [x] I included enough detail to reproduce or investigate the problem.

Related but different: #15665 (a follow-up turn fails with `resetAt: null`) and #16233 (a queued background notification blocks snooze). Here the follow-up turns are not failures at all: they finish as `completed`, so the thread is never armed for auto-resume.

Filed by the user's Claude Code agent at their request. Everything below is sanitized: no conversation content, project names, paths or account details. Thread labels A and B are arbitrary.

## Area

apps/server

## Summary

With **Auto-resume limited threads** and **Snooze limited threads** both on, a Claude thread that hit its five-hour limit while background subagents were running was never scheduled to resume. Its limited run stored a valid future reset, but about 2 seconds later a background-task notification started a new root run that finished as `completed` in under 2 seconds. Five more such runs followed. The thread's latest executed run was therefore `completed`, `limitRecovery` stayed `null`, no `limit-resume:` message was ever dispatched, and the thread sat idle for 4 h 42 m after the reset until the user sent a message.

A second Claude thread on the same account, where no further run followed the limited one, hit the same limit window about 73 seconds later. It was armed and resumed 1 second after the reset.

## Steps to reproduce

Observed incident on an unmodified official nightly. Not a minimized reproduction, and no fresh quota-consuming run was made.

1. Settings: `autoResumeLimitedThreads: true`, `snoozeLimitedThreads: true`.
2. In a Claude (`claudeAgent`, Orchestrator V2) thread, start several background subagents (11 in this incident) and let the root turn end normally while they work.
3. Exhaust the five-hour usage window. A background-task notification starts a root run that fails with `usage_limit`.
4. Other background subagents die on the same limit. Each one's completion notification starts another root run within seconds.
5. Wait for the reported reset time.

The reproduction in #15665 (real CLI against a mock Messages API) should be adaptable: after the first limited turn, let a background task complete so its wake turn starts within a few seconds.

## Expected behavior

The limited run is armed for recovery and the thread resumes at the reset time, exactly as the comparison thread did. A wake turn that makes no progress because the account is limited should not cancel or hide a pending recovery.

## Actual behavior

Thread A, read-only from the V2 projection tables. `t0` is the moment the limited run failed. `R` is the stored reset, `t0 + 4 h 20 m`.

| Run | Started by | Ended | Ran for | Status | Turn items |
| --- | --- | --- | --- | --- | --- |
| 2 | background-task notification | `t0` | 30.5 s | `failed` | ..., `assistant_message` (the provider's limit text), `error` with `failure.class: usage_limit`, `failure.code: api_error_429`, `failure.resetAt: R` |
| 3 | background-task notification | `t0 + 2.1 s` | 1.8 s | `completed` | `notification`, `checkpoint` |
| 4 | background-task notification | `t0 + 6.8 s` | 0.6 s | `completed` | `notification`, `checkpoint` |
| 5 | background-task notification | `t0 + 9.8 s` | 0.8 s | `completed` | `notification`, `checkpoint` |
| 6 | background-task notification | `t0 + 34.3 s` | 0.7 s | `completed` | `notification`, `system_notice`, `checkpoint` |
| 7 | background-task notification | `t0 + 41.0 s` | 1.3 s | `completed` | `notification`, `assistant_message` (61 characters: `You've hit your session limit · resets 3am (America/New_York)`), `checkpoint` |
| 8 | background-task notification | `t0 + 5 m 14 s` | 0.6 s | `completed` | `notification`, `system_notice`, `checkpoint` |

- Thread A `limitRecovery`: `null`. It was never armed.
- Thread A turn items matching `limit-resume`: none.
- Thread A next activity: a user message at `R + 4 h 42 m`.
- Runs 3 to 8 carry no `error` item. Run 7 stores the provider's limit message as an ordinary completed assistant message.

Thread B, same account and same limit window, no run after the limited one:

| Event | Time |
| --- | --- |
| Limited run failed (`usage_limit`, `resetAt: R`) | about `t0 + 73 s` |
| `limitRecovery` armed | `{ autoResume: true, snooze: true, resetAt: R }` |
| `limit-resume` run requested | `R + 1 s` |
| That run | `completed`, and the thread kept working |

## Analysis

Read from the installed server bundle of this build, not from a source checkout.

- `ProjectionStore.getLimitRecoveryCandidates` selects each thread's latest executed run (the run that ended last) and requires `r.status = 'failed'` plus a `usage_limit` error item with a non-null `resetAt` on that same run.
- `limitRecoveryCommand` in the usage-limit recovery worker returns `null` unless `thread.status === "failed"` and `thread.lastErrorClass === "usage_limit"`.
- So a wake run that ends as `completed` becomes the latest executed run and removes the thread from the candidate set. In this incident that happened 2.1 s after the limit stop, before the limited run had been armed.
- Separately, a turn that could not do any work because the account was limited was classified `completed`. In run 7 the only output is the provider's limit text.

Why this is not covered by a fix for #15665: carrying the reset onto follow-up *failures* does not help when the follow-up turns are not recorded as failures.

Possible directions, for the maintainers to judge:

- Classify a wake turn whose provider result is the limit message or a 429 as a `usage_limit` failure, including when a background-task notification started it.
- Let candidate selection use the most recent `usage_limit` failure whose reset is still in the future, ignoring later notification-only runs that produced no work.
- Hold background-task wake turns while the thread is limited, and release them with the resume.

## Impact

Major degradation or frequent failure.

Auto-resume silently does nothing for threads that have background agents running when the limit hits, which is the pattern most likely to exhaust a limit. Nothing resumed the thread and its work idled overnight. I did not capture how the thread was presented in the UI at the time.

## Version or commit

T3 Code (Nightly) `0.0.46-nightly.20261005.2702`, official Windows build. Claude Code CLI `2.1.292`.

## Environment

- Windows 11 Pro 10.0.26200, desktop app with its local server.
- Provider `claudeAgent`, model `claude-opus-5-5`, Claude subscription login, Orchestrator V2.
- The machine did not sleep during the window (no Kernel-Power sleep or wake events), and the same server process was running before the limit and after the reset.

## Logs or stack traces

None retained. `server.trace.ndjson` rotates at 10 MB roughly every three minutes here, so the files covering the limit stop and the reset were gone by morning. The evidence above comes from `orchestration_v2_projection_runs`, `orchestration_v2_projection_turn_items` and `orchestration_v2_projection_threads`, opened read-only.

## Workaround

Send a message to the thread after the reset. Avoid launching many background agents when the usage window is nearly spent.

