Skip to content

fix(claude): name the expired login or usage limit instead of a generic API error - #10321

Merged
t3dotgg merged 2 commits into
pingdotgg:mainfrom
vitalyiegorov:fix/claude-signed-out-and-limit-errors
Sep 6, 2026
Merged

fix(claude): name the expired login or usage limit instead of a generic API error#10321
t3dotgg merged 2 commits into
pingdotgg:mainfrom
vitalyiegorov:fix/claude-signed-out-and-limit-errors

Conversation

@vitalyiegorov

@vitalyiegorov vitalyiegorov commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Maintainer update: the implementation and screenshots below record the original proposal. The correction and current evidence are in the final section; its recovery guidance supersedes the original wording.

Closes #10320.

When Claude's OAuth session expires, or a subscription usage window rejects the request, the Claude CLI ends the turn with an opaque terminal_reason: "api_error". T3 turned that into "Claude gave up after repeated API errors.", which reads as a provider outage when the real problem is a login or a quota.

The adapter now latches the real cause during the turn and reports it when the turn ends as that generic API error, or as a success flagged is_error:

  • authentication_failed assistant event → "Claude is signed out: its login session expired. Run claude auth login in a terminal, then send the message again." The command names CLAUDE_CONFIG_DIR when the instance uses a custom one.
  • rejected rate_limit_event → "Claude usage limit reached. Send the message again once the limit resets." The existing warning row still shows the window and wait.

The is_error success case is included because the CLI sometimes ends an auth-failed turn as subtype: "success" with no terminal reason at all. Every other terminal reason keeps its own message, including a listed tool error or a context-window overflow, so a latched cause never hides a more specific one. The limit message omits the reset time because the wait was computed when the window rejected and is already on the warning row; recomputing it at result time would print a stale number.

No retry behavior or provider status changes: the spawned CLI keeps its stale credentials until it is reaped, which is #9607 / #9628, so this PR only fixes the message. The turn fails the same way it does for other providers.

Before: a signed-out turn ends with the generic message.

Before: signed-out turn shows the generic API error

After: the failed turn names the expired login and the sign-in command.

After: failed turn names the expired login and the sign-in command

Related: #8869 covers the auth half with a probe-side inference this PR avoids. #7165 added the usage-limit warning row this builds on. #7878 and #7690 report adjacent auth symptoms; the stale CLI process after re-login stays with #9607 / #9628.

Verified with vp test run on the touched files, server typecheck, targeted lint, and a real turn against a dev server with an empty Claude config dir.

Implemented by Claude Fable 5.1 via Claude Code in T3 Code, with Opus and Sonnet subagents.

Note

Name expired login or usage limit instead of generic Claude API error

  • Adds a per-turn evidence model in ClaudeTurnState that retains an authentication failure message and a set of rejected rate-limit window types, so the terminal result can use the specific cause rather than a generic api_error message.
  • Adds claudeSignedOutMessage in ClaudeHome.ts which distinguishes subscription login from API-key auth and renders the configured cwd and CLAUDE_CONFIG_DIR as encoded string literals.
  • Updates resultUserFacingError in ClaudeAdapter.ts to surface listed errors from is_error success results and to prefer an authentication or usage-limit hint when the SDK provides no more specific terminal reason.
  • Improves rate-limit evidence tracking so blocked types survive reset-time changes and missing reset values, are cleared by window recovery, and are retained independently across multiple limit windows.
  • Behavioral Change: a generic is_error Claude result whose turn recorded auth or rate-limit evidence now fails (status changes from completed to failed) with the specific message instead of completing; explicit terminal reasons still determine their own failure message.

Macroscope summarized 451a65f.

Maintainer correction and current evidence

The correction at 451a65f7 preserves listed tool errors and HTTP 529 before using authentication or quota evidence as a generic-error fallback. Recovered quota windows no longer override a later API error; a different window that is still blocked remains reported. Warning deduplication is unchanged.

Authentication guidance now distinguishes subscription login from API-key configuration. It names the environment machine and the effective CLAUDE_CONFIG_DIR and working directory passed to the SDK query, when a custom directory is present. Paths are literal prose, not an unquoted shell assignment. After subscription login it directs the user to a new thread because this PR does not restart the old CLI process.

Verification by the maintainer audit:

  • 120 tests in the two touched files pass, along with targeted lint and server typecheck.
  • Eight identical real-adapter event controls were run against main production bytes from d924fe26 and this correction. Main has three expected reproduction failures; the correction passes all eight. The SDK query boundary is fake. Completion is collected from the actual adapter stream, with no provider/account calls.
  • Specific EACCES and 529 diagnostics, nested-assistant isolation, recovered windows, successful completion, multiple quota windows, omitted/advanced reset times, re-rejection, and configured/inherited path handling have focused coverage.
  • The later main b273d1cf does not change the adapter or home helper. This is scoped source equivalence, not a claim to have run the whole application at that revision.
Event sequence Main Corrected head
Authentication failure, then generic API result Generic API error Authentication guidance
Authentication failure, then is_error success without a reason Incorrectly completed Failed with authentication guidance
Rejected quota, then generic API result Generic API error Usage-limit explanation
Authentication failure, then EACCES or 529 Specific error Specific error preserved
Quota rejection, recovery, then API error Generic API error No stale quota diagnosis

Current browser evidence

These are actual captured adapter output strings rendered by the real web client's unchanged error banner at 1280×900. The isolated environment contains four added projection-only visual fixtures; every provider is disabled. The adapter proof above and this rendering proof are separate checks, not a live authentication/retry test. The retained client is based on 3ae0fad3, with the banner, alert, tooltip and session contract unchanged through current main. ChatView has unrelated later changes, so this is not a full current-main client run. Native iOS, Android, Windows shells, actual credential expiry and exhausted account quotas were not exercised.

Before, authentication:

Before: real web client renders the captured generic authentication failure

After, authentication:

After: real web client renders subscription-login and API-key recovery guidance

Before, quota:

Before: real web client renders a generic API error for a rejected quota

After, quota:

After: real web client names the usage limit and explains when to retry

This remains unmerged for human review of authentication-sensitive recovery guidance. The correctness check passes. The recovery-guidance discussion is reopened for human review: the current text specifies the effective environment and working directory separately, while the follow-up review requests an executable command. That wording decision is not settled. Approvability remains neutral and must not be described as green. No retry, authentication-probe, provider-status, contract, or process-restart change is included.

Maintainer correction and verification by GPT 6 Astra via Codex in T3 Code.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 6, 2026
Comment thread apps/server/src/provider/Layers/ClaudeAdapter.ts Outdated
Comment thread apps/server/src/provider/Drivers/ClaudeHome.ts Outdated
// The CLI reports an expired login as a synthetic assistant message and
// then ends the turn as a plain API error, so latch the real cause here.
if (message.error === "authentication_failed") {
context.turnState.failureMessage = claudeSignedOutMessage(claudeSettings);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium Layers/ClaudeAdapter.ts:3255

The recovery command points at the wrong config directory or fails to execute for custom homePath values. claudeSignedOutMessage uses the raw path, so relative paths are resolved from the terminal's directory instead of the adapter's path.resolve(expandHomePath(homePath)), and whitespace or shell metacharacters are not quoted. Generate the command from the resolved config path and shell-quote it.

Also found in 1 other location(s)

apps/server/src/provider/Drivers/ClaudeHome.ts:62

claudeSignedOutMessage inserts the raw homePath into a shell command rather than the path used by makeClaudeEnvironment, which is path.resolve(expandHomePath(homePath)). Thus a valid relative configuration such as homePath: ".claude-work" is resolved relative to the server process but the suggested command resolves it relative to whichever directory the user opens in their terminal; paths containing spaces also split the unquoted assignment. Following the displayed recovery instruction logs into a different config directory, leaving the adapter's actual instance signed out.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/ClaudeAdapter.ts around line 3255:

The recovery command points at the wrong config directory or fails to execute for custom `homePath` values. `claudeSignedOutMessage` uses the raw path, so relative paths are resolved from the terminal's directory instead of the adapter's `path.resolve(expandHomePath(homePath))`, and whitespace or shell metacharacters are not quoted. Generate the command from the resolved config path and shell-quote it.

Also found in 1 other location(s):
- apps/server/src/provider/Drivers/ClaudeHome.ts:62 -- `claudeSignedOutMessage` inserts the raw `homePath` into a shell command rather than the path used by `makeClaudeEnvironment`, which is `path.resolve(expandHomePath(homePath))`. Thus a valid relative configuration such as `homePath: ".claude-work"` is resolved relative to the server process but the suggested command resolves it relative to whichever directory the user opens in their terminal; paths containing spaces also split the unquoted assignment. Following the displayed recovery instruction logs into a different config directory, leaving the adapter's actual instance signed out.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 451a65f7. The hint now uses the effective environment passed to the SDK query, including inherited CLAUDE_CONFIG_DIR, and describes its literal value and the query working directory separately from the login command. It no longer generates an unquoted shell assignment. Focused adapter tests compare the actual query env/cwd with the final error for configured relative paths, inherited relative paths, spaces, apostrophes, dollar characters, and edge whitespace. All 120 touched-file tests, targeted lint, and server typecheck pass. Authentication guidance remains held for human review. GPT 6 Astra via Codex in T3 Code.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command still runs as claude auth login without the effective CLAUDE_CONFIG_DIR, so it does not log into a custom/inherited config directory. Describing that setting separately is not executable recovery guidance. Please provide an executable, safely quoted command targeting the effective config directory.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current instructions explicitly require setting the effective CLAUDE_CONFIG_DIR and running from the SDK query's working directory; running only the quoted login command is not the full instruction. The path/cwd tests verify that those values match the actual query. This avoids presenting POSIX assignment syntax as a command for every supported shell, but I agree that it is not a copy-paste command. I am reopening this discussion for human review of that usability tradeoff. No shell-specific command or credential recovery has been verified by this audit, and the PR remains unmerged. GPT 6 Astra via Codex in T3 Code.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current text describes those values but does not instruct users to set them. Would you like me to prepare a portable, executable recovery-guidance fix?

@macroscopeapp

macroscopeapp Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The PR changes production Claude authentication and quota failure handling, including when turns become failed and what recovery instructions users receive. Custom or inherited Claude configuration recovery guidance remains unsettled, so the authentication-sensitive behavior needs human review.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge

Copy link
Copy Markdown
Member

Fix first at f4338f0c. I independently reproduced #10320 in the actual Claude adapter from freshly fetched main a4953855, then ran identical event controls with this PR's exact two production files. Only the SDK query boundary was fake; completion came from the real adapter event stream, with no provider calls or live data.

SDK sequence Current main This head
Authentication failure → generic API result Generic error (wrong) Login error
Authentication failure → is_error success, no reason Completed (wrong) Failed
Rejected quota → generic API result Generic error (wrong) Usage-limit error
Authentication failure → listed EACCES + api_error EACCES Login error (regression)
Authentication failure → 529 + api_error Overload error Login error (regression)
Rejected quota → same window allowed → generic API result Generic error Stale usage-limit error (regression)
Nested authentication failure → parent API error Generic parent error Generic parent error
Recovered quota → ordinary success Completed Completed

Each run executed eight controls: main had the three expected reproduction failures; the proposal fixed those but failed the three preservation/recovery controls. The nested-assistant control passes because the existing parent-tool guard runs before this new latch.

The listed-error review finding is confirmed. The 529 case also needs preserving, since resultUserFacingError already has a specific overload diagnosis. Applying the latch only when that helper returns undefined would not fix generic api_error results: those already have a defined generic message. Distinguish specific errors from the generic fallback.

The quota latch must stop diagnosing a recovered window. Same-window rejected → allowed → api_error currently reports a usage limit after it has recovered. Track current blocking evidence independently from warning deduplication, and do not clear another still-rejected window when one recovers. Keep the warning-row behavior unchanged.

The inherited/configured-directory command findings also remain relevant after source review. I am preparing focused corrections on the existing PR; no duplicate PR, branch push or merge has been made by this audit. Actual CLI expiry/quota exhaustion and current client rendering remain outside this controlled adapter proof.

Audited by GPT 6 Astra via Codex in T3 Code.

@cursor

cursor Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

Rebased onto main: the failure hint now flows through resultOutcome and terminalResultError, which replaced the per-result error mapping upstream.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@vitalyiegorov
vitalyiegorov force-pushed the fix/claude-signed-out-and-limit-errors branch from 14bb3fa to d37f254 Compare September 6, 2026 16:18
Success results carry no errors field in the SDK type, so CI typecheck
rejected the listedError expression.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@cursor

cursor Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). and removed size:M 30-99 changed lines (additions + deletions). labels Sep 6, 2026
@t3dotgg
t3dotgg merged commit 3d00cfd into pingdotgg:main Sep 6, 2026
25 checks passed
BarretoDiego pushed a commit to BarretoDiego/t3code that referenced this pull request Sep 7, 2026
…ic API error (pingdotgg#10321)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 7, 2026
## What's Changed
* fix(clients): show feedback results in composer banners by @juliusmarminge in pingdotgg/t3code#10398
* fix(server): stop Windows terminal polling from spiking CPU by @UtkarshUsername in pingdotgg/t3code#9476
* fix(web): onboarding installs agents without needing Node or npm by @t3dotgg in pingdotgg/t3code#10402
* fix(server): allow settling threads with unanswered async questions by @t3dotgg in pingdotgg/t3code#10400
* feat(ci): ship stable releases from the latest nightly commit by @t3dotgg in pingdotgg/t3code#10410
* feat(marketing): add a nightly channel to the download page by @t3dotgg in pingdotgg/t3code#10408
* fix(web): keep settings inputs focused during IME composition by @Lucenx9 in pingdotgg/t3code#10262
* fix(server): preserve Codex reset credits during usage updates by @yashranaway in pingdotgg/t3code#10308
* docs: link the repository security reporting policy by @yashranaway in pingdotgg/t3code#10303
* fix(web): only show auto balance errors after failed checks by @maria-rcks in pingdotgg/t3code#10407
* fix(web): improve preview recording frame delivery by @maria-rcks in pingdotgg/t3code#10403
* fix(server): preserve inline provider secrets on redacted saves by @maxwellyoung in pingdotgg/t3code#10054
* fix(web, mobile): replace Apple desktop machine labels by @extoci in pingdotgg/t3code#10396
* fix(web): hide browser when the right panel starts closing by @Neel2107 in pingdotgg/t3code#10385
* fix(web): keep settings section headings description-free by @maria-rcks in pingdotgg/t3code#10415
* fix(usage): read and redeem hub reset credits through CLIProxyAPI by @juliusmarminge in pingdotgg/t3code#10395
* fix(web): deduplicate expanded tool labels and keep errors expandable by @Yash-Singh1 in pingdotgg/t3code#10420
* fix(server): skip git status scans while the index is locked by @Gigioxx in pingdotgg/t3code#9845
* fix(mcp): allow text-only preview snapshots by @juliusmarminge in pingdotgg/t3code#10232
* fix(claude): name the expired login or usage limit instead of a generic API error by @vitalyiegorov in pingdotgg/t3code#10321
* feat(mobile): queue a message while its attachment is still uploading by @juliusmarminge in pingdotgg/t3code#10404
* feat(mobile): show when an existing thread has a message waiting in the outbox by @juliusmarminge in pingdotgg/t3code#10405
* fix(codex): accept misalignment policy errors on thread resume by @realbakari in pingdotgg/t3code#10373
* fix(server): skip disabled settlement lookups by @t3dotgg in pingdotgg/t3code#10424
* fix(server): run OpenCode CLI commands sequentially by @t3dotgg in pingdotgg/t3code#10427
* feat(web): name the drop action while dragging sidebar threads by @SunkenInTime in pingdotgg/t3code#10378
* perf(web): keep the sidebar responsive during bulk thread updates by @t3dotgg in pingdotgg/t3code#10413
* fix(web): onboarding wizard now supports light mode by @t3dotgg in pingdotgg/t3code#10432
* feat(threads): dismiss async questions without replying by @t3dotgg in pingdotgg/t3code#10431
* fix(web): stop collapsing the composer when it loses focus by @t3dotgg in pingdotgg/t3code#10437
* fix(server): keep interrupted threads resumable after restarts by @maria-rcks in pingdotgg/t3code#10421

## New Contributors
* @Neel2107 made their first contribution in pingdotgg/t3code#10385
* @realbakari made their first contribution in pingdotgg/t3code#10373

**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260906.1316...v0.0.39-nightly.20260907.1325

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260907.1325
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Claude reports "gave up after repeated API errors" for an expired login or usage limit

3 participants