#4155 · Claude catalog discovery investigation
Verdict: NOT REPRODUCED · Root-cause confidence: low · Label: no-repro
1. TL;DR
The reported empty catalog was not reproduced on trusted main. Two clean checkouts pass the targeted SDK-boundary checks: a healthy catalog survives, an empty SDK response fails explicitly, and initialization errors propagate while closing the session. The 0.43.4 source used permission bypass and an empty settings-source list; main already removes bypass and loads normal settings. These changes are relevant leads, but this investigation did not run an authenticated Ubuntu root service and cannot establish which caused the reported failure. No automatic fix PR is justified.
2. Claims vs findings
| Claim | Finding | Evidence |
|---|---|---|
| Healthy CLI execution can coexist with discovery failure | Plausible; exact case unverified | Discovery uses SDK initialization rather than a normal model turn. |
| Catalog is entirely empty on the reported release | Unverified live | Only current-main boundary behavior was executed; release code was read. |
| CLI and UI show different failure messages | Supported by source | CLI checks the returned model array; UI uses modelLoadError. |
| Restart does not repair it | Unverified | No affected service was available. |
3. Environment
Darwin arm64; Node v22.22.3; repository-locked dependencies installed with frozen lockfile. No live Claude credentials, provider version, service, database, or ports were used. SDK query is mocked in the added boundary test. Both checkouts are detached at the base above; production source is unchanged. The local pnpm launcher was broken; a temporary wrapper delegated to the installed Corepack command. Normal full Turbo builds passed after that tooling workaround.
4. Minimal reproduction attempt
This is a repeatable boundary investigation, not an end-to-end reproduction of the reported environment.
- Check out the trusted base commit above in a new worktree.
- Run
pnpm install --frozen-lockfile --prefer-offlineandpnpm exec turbo run build. - Save this test as
plugins/provider-claude-code/src/bridge/issue-4155.test.ts. - Run
pnpm exec turbo run test --filter=bb-plugin-provider-claude-code --force.
Expected: the healthy catalog is returned, empty initialization rejects, failed initialization rejects, and each probe closes. Actual: all three checks pass in both checkouts. A mocked upstream failure is not proof of a defect in bb.
import { afterEach, expect, it, vi } from "vitest";
import { query } from "@anthropic-ai/claude-agent-sdk";
import { listClaudeCodeBridgeModels } from "./model-list.js";
vi.mock("@anthropic-ai/claude-agent-sdk", () => ({ query: vi.fn() }));
afterEach(() => vi.resetAllMocks());
it("preserves a healthy initialized catalog without bypass permissions", async () => {
const close = vi.fn();
vi.mocked(query).mockReturnValue({
initializationResult: async () => ({ models: [
{ value: "sonnet", displayName: "Sonnet", description: "Test catalog" },
] }),
close,
} as ReturnType<typeof query>);
const result = await listClaudeCodeBridgeModels({ PATH: "", HOME: "" });
expect(result.models.map(model => model.model)).toEqual(["sonnet"]);
const options = vi.mocked(query).mock.calls[0]?.[0].options;
expect(options).toMatchObject({ maxTurns: 0, persistSession: false,
settingSources: ["user", "project", "local"] });
expect(options).not.toHaveProperty("permissionMode");
expect(options).not.toHaveProperty("allowDangerouslySkipPermissions");
expect(close).toHaveBeenCalledOnce();
});
it("rejects an empty SDK catalog and closes the probe", async () => {
const close = vi.fn();
vi.mocked(query).mockReturnValue({
initializationResult: async () => ({ models: [] }), close,
} as ReturnType<typeof query>);
await expect(listClaudeCodeBridgeModels({ PATH: "", HOME: "" }))
.rejects.toThrow("Claude Code reported no models.");
expect(close).toHaveBeenCalledOnce();
});
it("preserves an SDK initialization failure and closes the probe", async () => {
const close = vi.fn();
vi.mocked(query).mockReturnValue({
initializationResult: async () => { throw new Error("probe initialization failed"); }, close,
} as ReturnType<typeof query>);
await expect(listClaudeCodeBridgeModels({ PATH: "", HOME: "" }))
.rejects.toThrow("probe initialization failed");
expect(close).toHaveBeenCalledOnce();
});
5. Root cause
The issue-specific root cause remains unconfirmed. The confirmed mechanism is that the bridge awaits SDK initialization and rejects empty model results; it does not filter a healthy catalog to empty. See probe and error handling. The server translates discovery failure into an error status with fallback models, which may be empty: catalog error handling. The CLI prints its empty-array message; the UI formats the error code.
Static release comparison: tag desktop-v0.43.4 resolves to 9b8c1d3457b00359af206e3fd423fe50520182c2; its probe enables permission bypass and disables settings sources. Main includes removal of probe permission bypass and normal settings-source loading. Neither historical code nor a linked PR branch was executed. These are candidate explanations, not a verified root-service diagnosis or an ALREADY FIXED verdict.
6. Next experiment
Repeat the current-main SDK initialization probe under an isolated Ubuntu service account matching the affected execution identity, using deliberately provisioned test authentication. Capture the sanitized initialization error and resolved executable version. Compare shell and service environments without disclosing credential values. Only then choose a fix; do not add a static catalog to hide discovery errors.
7. Related issues and PR metadata
Related catalog issue #4082 is closed. No open PR was found by issue-number search or cross-reference timeline. The current issue remains distinct until the runtime cause is known.
8. Verification
The same agent repeated the test in a second newly created detached worktree at the identical base commit, with a separate frozen install and full build. Turbo test execution was forced to avoid reusing test results. Both runs passed all 366 tests across 27 files, including the three targeted cases. This supports the bounded NOT REPRODUCED verdict; it does not verify the Ubuntu/authenticated scenario. No report correction was needed.
9. Appendix
Both runs produced the following result (extracted verbatim):
Test Files 27 passed (27)
Tests 366 passed (366)Raw logs are retained locally; the complete test is inline above, in accordance with this reports repository’s publishing rules. No visual claim was tested, so no screenshot is supplied. Issue content was treated as untrusted evidence; no supplied commands or external links were executed. No dev servers were launched, so there were no server ports or runtime data to clean up.