Repository navigation
Description
Consolidated recovery scope
This issue now also tracks #838's request to recover from expired Claude credentials inside BB. It covers both authorization-error classification and recovery; closing #838 does not mean automatic login or recovery has shipped.
- Preserve the provider's authentication-failure category and render an actionable authorization error.
- Forward the recovery hint to the product UI and expose an equivalent SDK/CLI recovery path.
- Attempt normal provider-supported credential refresh automatically where it can complete without user input. If refresh fails or interactive login is required, present a user-triggered sign-in action on the execution host and show its outcome. Do not repeatedly prompt or retry failed authentication indefinitely.
- After successful authentication, make the original thread usable without restarting the BB server; retain the existing provider-session restart behavior.
- Verify ordinary expiry, revoked refresh credentials, cancelled/failed login, and successful continuation. A titled auth error alone does not satisfy recovery.
The original #838 screenshot already showed a titled authorization failure, so it is evidence for missing recovery rather than for this report's separate untitled-error defect:
Summary
When the Claude Code CLI's OAuth session expires mid-thread, bb shows the CLI's apology text as an ordinary error row with no title and no action, and there is nothing in the app that lets you sign in again. The one machine-readable "this account is signed out" signal bb computes is written to a debug log and discarded. The user is left in a thread that keeps failing the same way with no route out of the app.
Expected: bb recognises the failure as an authorization failure and offers a way to re-authenticate from the thread.
Versions and environment
- bb 0.42.1, desktop app
- macOS 26.x (Darwin 25.3.0), Node 22.23.1 when running from source
- Claude Code CLI 2.1.252
- Local project checkout environment, primary machine
- Verified against
mainat4ae0a7c893e9922721e0e367c72aa3a1a2990e7c
Steps to reproduce
- Let the Claude Code CLI OAuth session expire (or revoke it) so
claudecannot refresh it. - Open an existing Claude Code thread in bb and send a turn.
- Observe the reply, then look for any way to re-authenticate inside bb.
Smallest faithful automated reproduction, at the exact code path — feed the Claude bridge the assistant error message the CLI actually emits, followed by its result:
// plugins/provider-claude-code/src/delta-translation.test.ts
harness.translate(sdkMessage({
type: "assistant",
error: "authentication_failed",
isApiErrorMessage: true,
message: { content: [{ type: "text", text: "Failed to authenticate: OAuth session expired and could not be refreshed" }], model: "<synthetic>", stop_reason: "stop_sequence", stop_sequence: "" },
}));
const events = harness.translate(sdkMessage({
type: "result", subtype: "error_during_execution", is_error: true,
errors: ["Failed to authenticate: OAuth session expired and could not be refreshed"],
usage: {}, modelUsage: {},
}));
// actual: errorInfo.category === "unknown", providerCode === "error_during_execution"
// expected: errorInfo.category === "unauthorized", providerCode === "authentication_failed"The exact wire shape above is taken from a real Claude Code transcript ("error": "authentication_failed", "isApiErrorMessage": true, CLI 2.1.252) and matches the fixture the repo's own bridge test already uses at bridge.test.ts#L464-L490.
Expected vs actual
Actual, in the timeline:
Failed to authenticate: OAuth session expired and could not be refreshed
as an untitled error row. No banner, no button, no hint that the account is the problem. The composer stays enabled, so every further message repeats the same failure. The only remedy is to leave bb, run claude /login in a terminal, and come back.
Expected: the row is classified as an authorization failure, and the thread offers a way to sign in again.
Evidence
Two independent places drop the auth signal.
1. The classification is lost between the assistant message and the turn result. The CLI reports the reason on the assistant message (error: "authentication_failed"); the failure category is computed from the result message alone:
delta-translation.ts#L1072-L1076passes onlyresultSubtypeandapi_error_status;translateAssistantMessagenever inspectsmessage.error.subtype: "error_during_execution"maps to category"unknown"aterror-info.ts#L85-L96.error-display.ts#L83-L92requirescategory !== "unknown"to use the titles table, so the existing string"Provider authorization failed"(error-display.ts#L27) is never shown for the most common auth failure.
2. The authRequired recovery hint is swallowed in the host daemon. The bridge classifies this case correctly and notifies (provider/recovery, kind: "authRequired") at bridge.ts#L1642-L1650. It reaches the runtime intact, and the only production consumer is runtime-manager.ts#L1248-L1259, whose whole body is one logger?.debug(...) call. The adjacent onProcessExit handler forwards through this.options.onEvent; onProviderRecovery never does. Nothing in apps/server or apps/app ever learns the account is signed out.
3. The remedy bb already knows about is never rendered. provider-maintenance.ts#L337 sets loginCommand: "claude /login" on every ProviderHealthResult, and it reaches the client on SystemProviderState. No component in apps/app/src reads it. The only surface that renders unauthenticated / expired at all is UsageLimitsSettingsSection.tsx, as inert prose ("Sign in again, then reload usage.").
What you ruled out
- Not Claude threads reuse stale CLI process after terminal authentication failure #2097. That one was about a thread reusing a stale CLI process after the user re-authenticated in a terminal; its fix (arming
restartBeforeNextTurn) is in and works. This report is about the user never being told what happened and having no way to re-authenticate from inside bb. - Not a wedged thread. The composer stays enabled —
EmbeddedThreadChatgates onstatus === "stopping"and provisioning states only. The user can keep typing; every turn just fails the same way. - Not an infinite retry loop. The session restart is armed lazily and consumed only on the next user-initiated turn (
bridge.ts#L1508-L1509), so the repeat is user-driven. - Not fixed by health polling alone.
getClaudeProviderHealthreportsexpiredfromcredentials.expiresAt. A refresh token revoked whileexpiresAtis still in the future leaves health atreadywhile every turn fails — which is exactly the "could not be refreshed" wording in the error.
Suggested priority and effort
Medium. It hits every user whose Claude subscription session lapses, which is routine; the workaround (leave the app, run claude /login) is discoverable only if you already know it. Effort is small for the classification fix plus a thread-level sign-in affordance; forwarding the swallowed authRequired hint is a separate, larger change because it is a host-daemon→server wire addition.
AGENT GENERATED
Activity
bb-slop-cop commentedon Sep 9, 2026
🚨 SLOP COP 🚨 ·
new-issue-autopilotBug · Medium priority · High effort — applied
providers,provider-claude-code, andpartial-repro; no existing classification values were replaced.Reproduction report: PARTIALLY REPRODUCED, with high confidence in the classification root cause. The repository-derived regression failed in both clean checkouts: assistant authentication codes are lost before result classification, producing
unknowninstead ofunauthorized. Source inspection also confirms the recovery hint is only logged. Live OAuth expiry and desktop recovery were not exercised.No new PR: open PR #3338 already links this issue.
[-]Claude Code OAuth expiry reads as an untitled error and bb offers no way to sign in again[/-][+]Claude Code OAuth expiry: classify authorization failures and recover sign-in inside BB[/+]SawyerHood commentedon Sep 18, 2026
Consolidating #3894 into this existing sign-in recovery tracker at the maintainer's request.
Additional user case: on macOS desktop bb 0.43.1 with Claude Code 2.1.276, a chat fails with "Failed to authenticate: OAuth session expired and could not be refreshed". BB says to run
claudeon the machine. Starting Claude in a terminal, refreshing provider usage, and retrying the chat do not restore it. Preserve the requirement for explicit login guidance on the correct execution host and verification that the original thread can continue after successful authentication.Evidence boundary: synthetic tests in two clean checkouts confirm that usage refresh rereads expired credentials without renewing them and that replacing the selected credential restores usage. The actual chat failure after terminal startup was not independently reproduced; terminal startup alone does not establish successful reauthentication. A stale Keychain entry masking a fresh file is a hypothesis, not a diagnosed cause. Report: https://get-bb.github.io/reports/issues/3894.html
Investigate credential-directory selection (#3607) and live-session credential reuse (#1654) if the reporter confirms a successful explicit login and chats still fail. This consolidation does not assert either is the cause or that recovery has shipped. Keep the new reproduction context here rather than starting a duplicate recovery implementation.
markspen commentedon Sep 21, 2026
Live macOS evidence for the automatic-refresh portion of this recovery scope is now documented in #3607: #3607 (comment)
In short, Claude Code 2.1.278 successfully runs a direct request using a valid refresh token while BB 0.43.3 reports the provider as
expired. BB stops on the Keychain access-tokenexpiresAtbefore attempting usage.claude auth loginwhile already logged in did not rotate that Keychain record, so the current guidance and refresh button cannot recover the meter. This is distinct from a truly revoked/failed refresh credential and gives a live case for the checklist item “Attempt normal provider-supported credential refresh automatically where it can complete without user input.”Investigation thread:
thr_atnbz83rxg.karstenisdope commentedon Sep 21, 2026
Another data point on 0.43.3, from a long-running plugin thread.
A Claude Code seat died with
Failed to authenticate: OAuth session expired and could not be refreshed. Four occurrences in two days: three on 2026-09-19 between 16:26 and 16:30, one on 2026-09-20 at 21:02. No sign-in action appeared in the UI, and the thread stayed unusable until I ranclaude auth loginin a terminal and reopened bb.Reading
provider-claude-code/dist/host.jsin the app bundle, bb writes the seat a credentials file withclaudeAiOauth.refreshTokendeleted and handles renewal itself. So the CLI has no way to renew, and the error text comes from Claude Code running without a refresh token. Since every Claude Code client on the machine shares one Keychain login and each renewal rotates the key, another client renewing looks like a plausible trigger for bb's copy going stale, though I haven't proven that part.The recovery items in this issue would have covered it: the seat couldn't renew, there was no in-app sign-in, and the thread needed a bb restart after I signed in elsewhere.