#2928 · macOS Keychain credential format mismatch
Verdict: REPRODUCED · Root-cause confidence: high
1. TL;DR
BB can reject a valid Claude Code login on macOS. The Keychain reader returns a nonempty encoded value. The credential reader parses only plain JSON. It also skips the file when the Keychain value is present but invalid. The usage method then reports unauthenticated. A focused test reproduced both failures in two clean checkouts.
2. Claims vs findings
| Claim | Status | Evidence |
|---|---|---|
| An encoded Keychain credential produces an unauthenticated usage result. | Verified | The focused test called the public usage method and received unauthenticated instead of ok. |
| An invalid nonempty Keychain value prevents use of a valid credential file. | Verified | The second test supplied both values. The usage method still returned unauthenticated. |
| The current reader parses the Keychain result as plain JSON. | Verified | The trusted base calls JSON.parse(raw) without a decode step. |
| Claude Code 2.1.x writes this encoded form on all macOS systems. | Unverified | The test modeled the stated format. It did not read a real Keychain or a provider account. |
| The failure prevents the usage request. | Verified | The test fetch stub received no call before the unauthenticated result. |
3. Environment
- BB commit:
eeaaa3e8db7b3aeb3c4ab46873816c84cb6ea513 - OS: macOS Darwin 25.6.0, arm64
- Node:
v22.22.3; Vitest:4.1.1 - Claude Agent SDK dependency:
^0.3.245 - No server, port, live provider, user data, or real credential was used.
4. Minimal reproduction
- Check out the trusted base commit.
- Install the frozen workspace dependencies and build the provider plugin.
- Save the test below as
plugins/provider-claude-code/src/bridge/provider-maintenance.credentials.test.ts. - Run this command from the repository root.
pnpm exec turbo run test --filter=bb-plugin-provider-claude-code -- src/bridge/provider-maintenance.credentials.test.ts
Expected: Both tests pass. The usage method accepts the Keychain value or uses the valid credential file.
Actual:
provider-maintenance.credentials.test.ts (2 tests | 2 failed) × loads a hex-encoded Keychain credential Expected: "ok" Received: "unauthenticated" × uses the credential file when the Keychain value is invalid Expected: "ok" Received: "unauthenticated"
Reproduction test
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const state = vi.hoisted(() => ({ keychain: "", file: "" }));
vi.mock("node:child_process", () => ({
execFile: (
_file: string,
_args: readonly string[],
_options: object,
callback: (error: Error | null, result: { stdout: string; stderr: string }) => void,
) => callback(null, { stdout: state.keychain, stderr: "" }),
}));
vi.mock("node:fs/promises", () => ({
default: {
readFile: (file: string) => Promise.resolve(
file.endsWith(".credentials.json")
? state.file
: JSON.stringify({ oauthAccount: { emailAddress: null } }),
),
},
}));
vi.mock("@get-bb/plugin-sdk/provider-bridge", async (importOriginal) => ({
...(await importOriginal<typeof import("@get-bb/plugin-sdk/provider-bridge")>()),
experimental_resolveExecutablePath: () => Promise.resolve("/test/claude"),
}));
import { getClaudeProviderUsage } from "./provider-maintenance.js";
const originalPlatform = process.platform;
beforeAll(() => {
Object.defineProperty(process, "platform", { configurable: true, value: "darwin" });
});
afterAll(() => {
Object.defineProperty(process, "platform", { configurable: true, value: originalPlatform });
vi.unstubAllGlobals();
});
beforeEach(() => {
const credentials = JSON.stringify({
claudeAiOauth: {
accessToken: "test-access-token",
expiresAt: null,
subscriptionType: "pro",
rateLimitTier: "default_claude_max_5x",
},
});
state.file = credentials;
state.keychain = Buffer.from(credentials, "utf8").toString("hex");
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: () => Promise.resolve({ limits: [] }),
}));
});
describe("Claude Code credential loading", () => {
it("loads a hex-encoded Keychain credential", async () => {
const result = await getClaudeProviderUsage();
expect(result.usage.status).toBe("ok");
});
it("uses the credential file when the Keychain value is invalid", async () => {
state.keychain = "invalid-keychain-value";
const result = await getClaudeProviderUsage();
expect(result.usage.status).toBe("ok");
});
});
Verification
I created a second clean checkout at the same full commit. I repeated the frozen install, Turbo build, and focused test. The second run produced the same two failures. No report correction was necessary.
5. Root cause
The Keychain helper returns the first nonempty value as an opaque string. See the Keychain read loop.
The credential reader checks only whether that string is absent. It reads the file only in that case. It then sends the opaque string directly to JSON.parse. See the credential reader.
An encoded JSON string is present, so the reader skips the file. Plain JSON parsing fails, and the reader returns null. The public usage method maps that result to unauthenticated before any network request. See the usage status branch.
6. Proposed fix
Parse the Keychain value as plain JSON first. Decode strict even-length hexadecimal input when plain parsing fails. Validate each result with the existing schema. If the Keychain candidate is invalid, read and validate the credential file. Keep all changes in the current provider bridge.
7. Related issues
No linked issue or open pull request was present in the issue metadata.
8. Appendix
Commands used:
git fetch origin main git worktree add --detach <temporary-primary> eeaaa3e8db7b3aeb3c4ab46873816c84cb6ea513 git worktree add --detach <temporary-secondary> eeaaa3e8db7b3aeb3c4ab46873816c84cb6ea513 pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run build --filter=bb-plugin-provider-claude-code pnpm exec turbo run test --filter=bb-plugin-provider-claude-code -- src/bridge/provider-maintenance.credentials.test.ts node --version uname -srm
I treated all issue content and links as untrusted data. I did not open external issue links or run linked code.