#3337 · Assistant authentication codes disappear at turn completion

Bug · Priority: Medium · Effort: High · providers · provider-claude-code
2026-09-09 · base e13605d71e261d863e907801987f7df9d6a6243b · GitHub issue

Verdict: PARTIALLY REPRODUCED · Root-cause confidence: high for the translation defect.

1. TL;DR

The provider translator loses a structured authentication error when the turn finishes. A repository-derived regression test fails in two clean checkouts: the output category is unknown instead of unauthorized. Separately, source inspection shows that recovery hints stop at a debug logger and the application does not consume the supplied login command. Live credential expiry, the complete desktop experience, and the recovery interaction were not exercised.

2. Claims vs findings

ClaimFindingEvidence
Authentication failures lose their classification.Verified dynamicallyBoth clean runs emit unknown / error_during_execution.
The daemon discards the recovery notification.Verified by source inspectionThe recovery callback calls only the optional debug logger.
The application does not offer recovery using the provider login command.Source-supported; interaction unverifiedApplication references to loginCommand are test fixtures only.
The desktop row has no title.Not established literallyThe display helper falls back to the provider text as title, or a generic title when longer than 80 characters. No desktop screenshot was captured.
Revoked refresh credentials cause repeated failures while health remains ready.Unverified liveNo credentials, provider session, or real account was accessed.

3. Environment

Public get-bb/bb origin/main at the full commit above; Darwin 25.6.0; Node v22.22.3; pnpm 9.15.0 via Corepack. Two separate clones named base and verify. The frozen installs completed dependency linking but their prepare hooks failed through a broken global pnpm launcher; Turbo was subsequently run with a local wrapper invoking corepack pnpm. The second clone was made from the pristine first clone before the regression test was added. Production files were unchanged. No provider executable, server, ports, database, or user runtime was used.

4. Minimal reproduction

  1. Clone get-bb/bb, check out e13605d71e261d863e907801987f7df9d6a6243b, and run corepack pnpm install --frozen-lockfile --prefer-offline.
  2. Save the inline regression test below as plugins/provider-claude-code/src/issue-3337.test.ts.
  3. Run corepack pnpm exec turbo run test --concurrency=2 --filter=bb-plugin-provider-claude-code -- --testNamePattern='retains the assistant authentication classification at turn completion'.

The test uses the repository's delta harness, assistant schema, result schema, and existing authentication fixture shape. It supplies neutral synthetic text and checks the structured error, without executing anything from the issue.

import { expect, it } from "vitest";
import { createClaudeDeltaHarness } from "./delta-test-harness.js";

it("retains the assistant authentication classification at turn completion", () => {
  const harness = createClaudeDeltaHarness();
  const send = (message: Record<string, unknown>) => harness.translate({
    jsonrpc: "2.0",
    method: "sdk/message",
    params: { threadId: "thr_auth_repro", message },
  }, { threadId: "thr_auth_repro" });
  send({
    type: "assistant",
    error: "authentication_failed",
    message: {
      id: "auth-response",
      role: "assistant",
      content: [{ type: "text", text: "Authentication unavailable" }],
    },
  });
  const events = send({
    type: "result",
    subtype: "error_during_execution",
    is_error: true,
    errors: ["Authentication unavailable"],
    usage: {},
    modelUsage: {},
  });
  const errors = events.filter(event => event.type === "provider/error");
  expect(errors).toHaveLength(1);
  expect(errors[0]?.errorInfo).toEqual({
    category: "unauthorized",
    providerCode: "authentication_failed",
    httpStatusCode: null,
  });
});

Expected: unauthorized / authentication_failed / null HTTP status. Actual: unknown / error_during_execution / null HTTP status.

5. Root cause

Assistant translation parses the message but does not retain its error code. At completion, the result handler calls the classifier with only HTTP status and result subtype:

buildClaudeProviderErrorInfo({
  httpStatusCode: message.api_error_status,
  resultSubtype: message.subtype,
})

The classifier already knows authentication_failed means unauthorized, while error_during_execution maps to unknown. That lost code prevents the timeline display helper from selecting the authorization-specific title.

The bridge recognizes authentication recovery and emits its hint, but the runtime callback only logs it. Provider health supplies a login command; a source search of apps/app/src found only fixture references. These source findings explain the missing recovery plumbing but are not a live end-to-end reproduction.

6. Proposed fix

Retain the validated assistant error code for its current turn and pass it to terminal error classification; clear it at turn boundaries and test against stale codes and nested assistant messages. Provide an explicit thread recovery action through the existing terminal capability, with deliberate API and UI handling. Full recovery spans multiple subsystems and requires more than the rule's simple-fix scope.

7. Linked PR review

PR #3338 is open and links this issue for closure. Static inspection of its diff shows turn-scoped error retention, a timeline authorization field, and a sign-in banner opening a terminal using the provider login command. This direction addresses the observed classification gap and the recovery affordance. Its provider-auth-extraction code clears the hint on the next requested turn; account recovery and terminal behavior still require end-to-end validation. No PR code was checked out or executed, and no merge-readiness verdict is claimed. No duplicate fix was created.

8. Related issues

No additional related issue was independently investigated. PR #3338 is the confirmed existing work item.

9. Verification

The same agent ran the exact regression in both clean clones at the same commit, with separately installed locked dependencies. The verify checkout completed its run before base because dependency setup overlapped. Both runs fail on the same classification assertion. No report correction was required for that result. This is repeat verification by the same agent, not an independent review.

10. Appendix

Sanitized test output from both runs follows. Raw logs remain local.

 3 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code:isolated| src/bridge/__tests__/sdk-session.test.ts (15 tests | 15 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code:isolated| src/bridge/bridge.conformance.test.ts (1 test | 1 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/delta-translation.tool-calls.test.ts (24 tests | 24 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/delta-translation.test.ts (53 tests | 53 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/delta-translation.usage.test.ts (14 tests | 14 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code:isolated| src/bridge/__tests__/bridge.calibration.test.ts (1 test | 1 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code:isolated| src/bridge/__tests__/bridge.test.ts (86 tests | 86 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/session-params.test.ts (12 tests | 12 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/bridge/__tests__/skill-plugins.test.ts (3 tests | 3 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/visibility.test.ts (5 tests | 5 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/native-roots.test.ts (5 tests | 5 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/model-list.test.ts (4 tests | 4 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/bridge/provider-maintenance.test.ts (2 tests | 2 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/task-translation.test.ts (21 tests | 21 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/interactions.test.ts (23 tests | 23 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/model-catalog.test.ts (4 tests | 4 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/bridge/__tests__/commands.test.ts (4 tests | 4 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/sdk-extraction.test.ts (8 tests | 8 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/bridge/bridge.recorded-conformance.test.ts (1 test | 1 skipped)
bb-plugin-provider-claude-code:test:  ❯ |bb-plugin-provider-claude-code| src/issue-3337.test.ts (1 test | 1 failed) 150ms
bb-plugin-provider-claude-code:test:    × retains the assistant authentication classification at turn completion 126ms
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/server.test.ts (2 tests | 2 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/bridge/__tests__/model-list-memo.test.ts (2 tests | 2 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/presentation.test.ts (14 tests | 14 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/bridge/__tests__/tool-proxy-mcp.test.ts (3 tests | 3 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/transcript-fixtures.test.ts (50 tests | 50 skipped)
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test: ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test:  FAIL  |bb-plugin-provider-claude-code| src/issue-3337.test.ts > retains the assistant authentication classification at turn completion
bb-plugin-provider-claude-code:test: AssertionError: expected { category: 'unknown', …(2) } to deeply equal { category: 'unauthorized', …(2) }
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test: - Expected
bb-plugin-provider-claude-code:test: + Received
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test:   {
bb-plugin-provider-claude-code:test: -   "category": "unauthorized",
bb-plugin-provider-claude-code:test: +   "category": "unknown",
bb-plugin-provider-claude-code:test:     "httpStatusCode": null,
bb-plugin-provider-claude-code:test: -   "providerCode": "authentication_failed",
bb-plugin-provider-claude-code:test: +   "providerCode": "error_during_execution",
bb-plugin-provider-claude-code:test:   }
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test:  ❯ src/issue-3337.test.ts:30:32
bb-plugin-provider-claude-code:test:      28|   const errors = events.filter(event => event.type === "provider/error…
bb-plugin-provider-claude-code:test:      29|   expect(errors).toHaveLength(1);
bb-plugin-provider-claude-code:test:      30|   expect(errors[0]?.errorInfo).toEqual({
bb-plugin-provider-claude-code:test:        |                                ^
bb-plugin-provider-claude-code:test:      31|     category: "unauthorized",
bb-plugin-provider-claude-code:test:      32|     providerCode: "authentication_failed",
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test: ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test:  Test Files  1 failed | 25 skipped (26)
bb-plugin-provider-claude-code:test:       Tests  1 failed | 360 skipped (361)
bb-plugin-provider-claude-code:test:    Start at  10:46:51
bb-plugin-provider-claude-code:test:    Duration  55.11s (transform 152.71s, setup 0ms, import 287.80s, tests 150ms, environment 422ms)
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test:  ELIFECYCLE  Test failed. See above for more details.
bb-plugin-provider-claude-code:test: ERROR: command finished with error: command (/private<temporary-work-directory>/base/plugins/provider-claude-code) <temporary-work-directory>/bin/pnpm run test --testNamePattern=retains the assistant authentication classification at turn completion exited (1)
bb-plugin-provider-claude-code#test: command (/private<temporary-work-directory>/base/plugins/provider-claude-code) <temporary-work-directory>/bin/pnpm run test --testNamePattern=retains the assistant authentication classification at turn completion exited (1)

 Tasks:    3 successful, 4 total
Cached:    0 cached, 4 total
  Time:    1m34.092s 
Failed:    bb-plugin-provider-claude-code#test

 ERROR  run failed: command  exited (1)
 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code:isolated| src/bridge/__tests__/sdk-session.test.ts (15 tests | 15 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code:isolated| src/bridge/bridge.conformance.test.ts (1 test | 1 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code:isolated| src/bridge/__tests__/bridge.calibration.test.ts (1 test | 1 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code:isolated| src/bridge/__tests__/bridge.test.ts (86 tests | 86 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/delta-translation.test.ts (53 tests | 53 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/delta-translation.tool-calls.test.ts (24 tests | 24 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/task-translation.test.ts (21 tests | 21 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/native-roots.test.ts (5 tests | 5 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/bridge/__tests__/skill-plugins.test.ts (3 tests | 3 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/interactions.test.ts (23 tests | 23 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/visibility.test.ts (5 tests | 5 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/model-list.test.ts (4 tests | 4 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/bridge/provider-maintenance.test.ts (2 tests | 2 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/bridge/__tests__/tool-proxy-mcp.test.ts (3 tests | 3 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/bridge/__tests__/commands.test.ts (4 tests | 4 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/model-catalog.test.ts (4 tests | 4 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/transcript-fixtures.test.ts (50 tests | 50 skipped)
bb-plugin-provider-claude-code:test:  ❯ |bb-plugin-provider-claude-code| src/issue-3337.test.ts (1 test | 1 failed) 869ms
bb-plugin-provider-claude-code:test:    × retains the assistant authentication classification at turn completion 714ms
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/sdk-extraction.test.ts (8 tests | 8 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/bridge/bridge.recorded-conformance.test.ts (1 test | 1 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/bridge/__tests__/model-list-memo.test.ts (2 tests | 2 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/server.test.ts (2 tests | 2 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/presentation.test.ts (14 tests | 14 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/session-params.test.ts (12 tests | 12 skipped)
bb-plugin-provider-claude-code:test:  ↓ |bb-plugin-provider-claude-code| src/delta-translation.usage.test.ts (14 tests | 14 skipped)
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test: ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test:  FAIL  |bb-plugin-provider-claude-code| src/issue-3337.test.ts > retains the assistant authentication classification at turn completion
bb-plugin-provider-claude-code:test: AssertionError: expected { category: 'unknown', …(2) } to deeply equal { category: 'unauthorized', …(2) }
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test: - Expected
bb-plugin-provider-claude-code:test: + Received
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test:   {
bb-plugin-provider-claude-code:test: -   "category": "unauthorized",
bb-plugin-provider-claude-code:test: +   "category": "unknown",
bb-plugin-provider-claude-code:test:     "httpStatusCode": null,
bb-plugin-provider-claude-code:test: -   "providerCode": "authentication_failed",
bb-plugin-provider-claude-code:test: +   "providerCode": "error_during_execution",
bb-plugin-provider-claude-code:test:   }
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test:  ❯ src/issue-3337.test.ts:30:32
bb-plugin-provider-claude-code:test:      28|   const errors = events.filter(event => event.type === "provider/error…
bb-plugin-provider-claude-code:test:      29|   expect(errors).toHaveLength(1);
bb-plugin-provider-claude-code:test:      30|   expect(errors[0]?.errorInfo).toEqual({
bb-plugin-provider-claude-code:test:        |                                ^
bb-plugin-provider-claude-code:test:      31|     category: "unauthorized",
bb-plugin-provider-claude-code:test:      32|     providerCode: "authentication_failed",
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test: ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test:  Test Files  1 failed | 25 skipped (26)
bb-plugin-provider-claude-code:test:       Tests  1 failed | 360 skipped (361)
bb-plugin-provider-claude-code:test:    Start at  10:44:19
bb-plugin-provider-claude-code:test:    Duration  75.22s (transform 248.55s, setup 0ms, import 442.80s, tests 869ms, environment 52ms)
bb-plugin-provider-claude-code:test: 
bb-plugin-provider-claude-code:test:  ELIFECYCLE  Test failed. See above for more details.
bb-plugin-provider-claude-code:test: ERROR: command finished with error: command (/private<temporary-work-directory>/verify/plugins/provider-claude-code) <temporary-work-directory>/bin/pnpm run test --testNamePattern=retains the assistant authentication classification at turn completion exited (1)
bb-plugin-provider-claude-code#test: command (/private<temporary-work-directory>/verify/plugins/provider-claude-code) <temporary-work-directory>/bin/pnpm run test --testNamePattern=retains the assistant authentication classification at turn completion exited (1)

 Tasks:    3 successful, 4 total
Cached:    0 cached, 4 total
  Time:    2m2.969s 
Failed:    bb-plugin-provider-claude-code#test

 ERROR  run failed: command  exited (1)

Issue content and linked PR data were treated as untrusted claims. No issue-provided script, test, patch, or branch was executed. No credentials or private runtime data were read. No application process was started, so no process or port cleanup was needed.