authorgravatar for 20579513+PieterVanZyl-Dev@users.noreply.github.comPieter van Zyl <20579513+PieterVanZyl-Dev@users.noreply.github.com> 2026-07-20 10:35:08+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-20 08:35:08+00:00
log68ea280388bed4cf9229a4667b4f8a7b5b076105
tree5a69fe5cddaba5260415dbdd577b991216b1f7d7
parentdfbda843628c817a48523b1a4a28a6ef232d1d65
signature Signed by PGP key B5690EEEBB952194

fix(server): recognize Bedrock-backed Claude as authenticated (#3931)


2 files changed, 50 insertions(+), 5 deletions(-)

apps/server/src/provider/Layers/ClaudeProvider.ts+24-5
......@@ -480,9 +480,19 @@ function claudeAuthMetadata(input: {
480480 return undefined;
481481}
482482
483function apiProviderAuthMetadata(
484 apiProvider: string | undefined,
485): { readonly type: string; readonly label: string } | undefined {
486 return apiProvider === "bedrock" ? { type: "bedrock", label: "Amazon Bedrock" } : undefined;
487}
488
483489// ── SDK capability probe ────────────────────────────────────────────
484490
485const CAPABILITIES_PROBE_TIMEOUT_MS = 8_000;
491// Amazon Bedrock initializes far slower than first-party auth: the SDK boots the
492// Bedrock backend and runs the `awsAuthRefresh` credential hook before returning
493// account info. The previous 8s budget expired mid-init, so the probe returned
494// `undefined` and left the provider unverified and unselectable in the picker.
495const CAPABILITIES_PROBE_TIMEOUT_MS = 25_000;
486496
487497function nonEmptyProbeString(value: string): string | undefined {
488498 const candidate = value.trim();
......@@ -493,6 +503,12 @@ type ClaudeCapabilitiesProbe = {
493503 readonly email: string | undefined;
494504 readonly subscriptionType: string | undefined;
495505 readonly tokenSource: string | undefined;
506 /**
507 * Active API backend reported by the SDK's `AccountInfo`. Anthropic OAuth
508 * login only applies when `"firstParty"`; for Amazon Bedrock (`"bedrock"`)
509 * the subscription/token fields are absent and auth is external AWS creds.
510 */
511 readonly apiProvider: string | undefined;
496512 readonly slashCommands: ReadonlyArray<ServerProviderSlashCommand>;
497513};
498514
......@@ -618,12 +634,14 @@ const probeClaudeCapabilities = (
618634 readonly email?: string;
619635 readonly subscriptionType?: string;
620636 readonly tokenSource?: string;
637 readonly apiProvider?: string;
621638 }
622639 | undefined;
623640 return {
624641 email: account?.email,
625642 subscriptionType: account?.subscriptionType,
626643 tokenSource: account?.tokenSource,
644 apiProvider: account?.apiProvider,
627645 slashCommands: parseClaudeInitializationCommands(init.commands),
628646 } satisfies ClaudeCapabilitiesProbe;
629647 });
......@@ -799,10 +817,11 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
799817 });
800818 }
801819
802 const authMetadata = claudeAuthMetadata({
803 subscriptionType: capabilities.subscriptionType,
804 authMethod: capabilities.tokenSource,
805 });
820 const authMetadata =
821 claudeAuthMetadata({
822 subscriptionType: capabilities.subscriptionType,
823 authMethod: capabilities.tokenSource,
824 }) ?? apiProviderAuthMetadata(capabilities.apiProvider);
806825 return buildServerProvider({
807826 presentation: CLAUDE_PRESENTATION,
808827 enabled: claudeSettings.enabled,
apps/server/src/provider/Layers/ProviderRegistry.test.ts+26
......@@ -100,6 +100,7 @@ type TestClaudeCapabilities = {
100100 readonly email: string | undefined;
101101 readonly subscriptionType: string | undefined;
102102 readonly tokenSource: string | undefined;
103 readonly apiProvider: string | undefined;
103104 readonly slashCommands: ReadonlyArray<ServerProviderSlashCommand>;
104105};
105106
......@@ -109,6 +110,7 @@ function claudeCapabilities(overrides: Partial<TestClaudeCapabilities> = {}) {
109110 email: undefined,
110111 subscriptionType: undefined,
111112 tokenSource: undefined,
113 apiProvider: undefined,
112114 slashCommands: [],
113115 ...overrides,
114116 });
......@@ -1492,6 +1494,30 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te
14921494 ),
14931495 );
14941496
1497 it.effect("returns ready and labels Bedrock-backed Claude as authenticated", () =>
1498 Effect.gen(function* () {
1499 // Bedrock authenticates via external AWS credentials, so the SDK init
1500 // reports only `apiProvider` with no subscription or token.
1501 const status = yield* checkClaudeProviderStatus(
1502 defaultClaudeSettings,
1503 claudeCapabilities({ apiProvider: "bedrock" }),
1504 );
1505 assert.strictEqual(status.status, "ready");
1506 assert.strictEqual(status.installed, true);
1507 assert.strictEqual(status.auth.status, "authenticated");
1508 assert.strictEqual(status.auth.type, "bedrock");
1509 assert.strictEqual(status.auth.label, "Amazon Bedrock");
1510 }).pipe(
1511 Effect.provide(
1512 mockSpawnerLayer((args) => {
1513 const joined = args.join(" ");
1514 if (joined === "--version") return { stdout: "1.0.0\n", stderr: "", code: 0 };
1515 throw new Error(`Unexpected args: ${joined}`);
1516 }),
1517 ),
1518 ),
1519 );
1520
14951521 it.effect("includes Claude Fable 5 on supported Claude Code versions", () =>
14961522 Effect.gen(function* () {
14971523 const status = yield* checkClaudeProviderStatus(