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: {...@@ -480,9 +480,19 @@ function claudeAuthMetadata(input: {
480 return undefined;480 return undefined;
481}481}
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
483// ── SDK capability probe ────────────────────────────────────────────489// ── 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
487function nonEmptyProbeString(value: string): string | undefined {497function nonEmptyProbeString(value: string): string | undefined {
488 const candidate = value.trim();498 const candidate = value.trim();
...@@ -493,6 +503,12 @@ type ClaudeCapabilitiesProbe = {...@@ -493,6 +503,12 @@ type ClaudeCapabilitiesProbe = {
493 readonly email: string | undefined;503 readonly email: string | undefined;
494 readonly subscriptionType: string | undefined;504 readonly subscriptionType: string | undefined;
495 readonly tokenSource: string | undefined;505 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;
496 readonly slashCommands: ReadonlyArray<ServerProviderSlashCommand>;512 readonly slashCommands: ReadonlyArray<ServerProviderSlashCommand>;
497};513};
498514
...@@ -618,12 +634,14 @@ const probeClaudeCapabilities = (...@@ -618,12 +634,14 @@ const probeClaudeCapabilities = (
618 readonly email?: string;634 readonly email?: string;
619 readonly subscriptionType?: string;635 readonly subscriptionType?: string;
620 readonly tokenSource?: string;636 readonly tokenSource?: string;
637 readonly apiProvider?: string;
621 }638 }
622 | undefined;639 | undefined;
623 return {640 return {
624 email: account?.email,641 email: account?.email,
625 subscriptionType: account?.subscriptionType,642 subscriptionType: account?.subscriptionType,
626 tokenSource: account?.tokenSource,643 tokenSource: account?.tokenSource,
644 apiProvider: account?.apiProvider,
627 slashCommands: parseClaudeInitializationCommands(init.commands),645 slashCommands: parseClaudeInitializationCommands(init.commands),
628 } satisfies ClaudeCapabilitiesProbe;646 } satisfies ClaudeCapabilitiesProbe;
629 });647 });
...@@ -799,10 +817,11 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(...@@ -799,10 +817,11 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
799 });817 });
800 }818 }
801819
802 const authMetadata = claudeAuthMetadata({820 const authMetadata =
803 subscriptionType: capabilities.subscriptionType,821 claudeAuthMetadata({
804 authMethod: capabilities.tokenSource,822 subscriptionType: capabilities.subscriptionType,
805 });823 authMethod: capabilities.tokenSource,
824 }) ?? apiProviderAuthMetadata(capabilities.apiProvider);
806 return buildServerProvider({825 return buildServerProvider({
807 presentation: CLAUDE_PRESENTATION,826 presentation: CLAUDE_PRESENTATION,
808 enabled: claudeSettings.enabled,827 enabled: claudeSettings.enabled,
apps/server/src/provider/Layers/ProviderRegistry.test.ts+26
...@@ -100,6 +100,7 @@ type TestClaudeCapabilities = {...@@ -100,6 +100,7 @@ type TestClaudeCapabilities = {
100 readonly email: string | undefined;100 readonly email: string | undefined;
101 readonly subscriptionType: string | undefined;101 readonly subscriptionType: string | undefined;
102 readonly tokenSource: string | undefined;102 readonly tokenSource: string | undefined;
103 readonly apiProvider: string | undefined;
103 readonly slashCommands: ReadonlyArray<ServerProviderSlashCommand>;104 readonly slashCommands: ReadonlyArray<ServerProviderSlashCommand>;
104};105};
105106
...@@ -109,6 +110,7 @@ function claudeCapabilities(overrides: Partial<TestClaudeCapabilities> = {}) {...@@ -109,6 +110,7 @@ function claudeCapabilities(overrides: Partial<TestClaudeCapabilities> = {}) {
109 email: undefined,110 email: undefined,
110 subscriptionType: undefined,111 subscriptionType: undefined,
111 tokenSource: undefined,112 tokenSource: undefined,
113 apiProvider: undefined,
112 slashCommands: [],114 slashCommands: [],
113 ...overrides,115 ...overrides,
114 });116 });
...@@ -1492,6 +1494,30 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te...@@ -1492,6 +1494,30 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te
1492 ),1494 ),
1493 );1495 );
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
1495 it.effect("includes Claude Fable 5 on supported Claude Code versions", () =>1521 it.effect("includes Claude Fable 5 on supported Claude Code versions", () =>
1496 Effect.gen(function* () {1522 Effect.gen(function* () {
1497 const status = yield* checkClaudeProviderStatus(1523 const status = yield* checkClaudeProviderStatus(