Repository navigation
[Bug]: A thread stopped by a usage limit is labelled Failed, indistinguishable from a crash #10545
Description
Activity
Thanks for the tight write-up and screenshots — this checks out on current main.
A usage-limit stop is supposed to be a failed turn with a readable message (#8358, #10321). That part is fine. The clients then have no structured way to tell it apart from a crash:
- Claude/Codex emit
runtime.errorwithclass: "provider_error"(ClaudeAdapter.emitRuntimeError, same slot inCodexAdapter).RuntimeErrorClasshas nousage_limittoday. - Ingestion copies only
payload.messageontoOrchestrationSession.lastErrorand setsstatus: "error". There is nolastErrorClass. resolveSidebarThreadStatusandresolveThreadListV2Statusmap every errored session tofailed, so the web row and the mobile list both say Failed in red.ThreadErrorBanneris hard-codedvariant="error"even thoughAlertalready has a warning tone.
So a scan of ten threads cannot tell “this one hit the window, retry after reset” from “this one crashed.”
Not a duplicate of #7314 (same blindness one level down, on subagents) or of #10544 / #10472 (those are about the sentence, not the list/banner classification). Complementary to #9012 (snooze from the reset signal). Do not reopen the #9236 approach of treating the turn as cancelled/warning-only.
Suggested fix, matching the report: add
usage_limitto the runtime error class the adapters already send, persist it on the session aslastErrorClassbesidelastError(contract +projection_thread_sessionscolumn), and let the web sidebar, mobile list, and thread banner (warning, not error) read it as an amber wait state — Limited, same family as Approval / Woke. No change to turn state, retries, or settlement. String-matchinglastErroris the wrong path.Accepting as a minor classification bug. Implementation can land independently of #10473 / #10546; those PRs should emit the new class once it exists.
- Claude/Codex emit
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.via-triageFiled through npx t3 triageFiled through npx t3 triage
on Sep 7, 2026 Thanks. The PR is in progress on that exact shape, with one trim: no new field on
turn.completed. Both adapters emit the classedruntime.errorbefore the failed completion, so ingestion carrieslastErrorClassforward on the failedturn.completedand clears it on the transition to ready, the same waylastErrorflows today. Once it lands, #10473 and #10546 each gain the one line that sets the class on their limit error.PR: #10550.
Before submitting
Area
apps/web
Steps to reproduce
Expected behavior
A thread that stopped because the account is out of quota reads as waiting, not broken: the row says something like Limited in the amber tone the other wait states use (Approval, Woke), and the thread banner is a warning, not an error. A user scanning ten threads should tell "this one hit the limit, it will work again at 9:20" from "this one crashed" without opening each.
Actual behavior
The row says Failed in red and the banner is the red error banner, identical to a provider crash or a transport error. Since #8358 and #10321 a usage-limit stop is deliberately a failed turn with a visible message, which is right for the turn state, but the clients have no way to tell the two failures apart:
OrchestrationSessioncarries onlylastError(a string) andstatus: "error", soresolveSidebarThreadStatus(apps/web/src/components/Sidebar.logic.ts) andresolveThreadListV2Status(apps/mobile/src/features/threads/threadListV2.ts) both map every errored session tofailed. The adapters already know the difference: the Claude adapter emits the limit as aruntime.errorwhose payload has aclassslot (provider_errortoday), and #10473 does the same for Codex.The thread on the left stopped on a limit; the row and banner call it a failure:
Suggested shape, keeping the failed-turn model: add
usage_limitto the runtime error class the adapters already send, carry it onto the session aslastErrorClassbesidelastError, and let the web sidebar, the mobile list, and the thread banner read it. No change to turn state, retries, or settlement.Related: #7314 (subagent status stays red after a limit recovery) is the same blindness one level down; #9012 offers the snooze from the same signal.
Impact
Minor bug or occasional failure
Version or commit
main @ 1d1bf50
Environment
macOS 15, desktop app; Claude Code CLI 2.1.263; server on Linux
Logs or stack traces
No response
Screenshots, recordings, or supporting files
No response
Workaround
Open the thread and read the banner text.