diff --git a/apps/server/src/provider/Drivers/ClaudeDriver.ts b/apps/server/src/provider/Drivers/ClaudeDriver.ts index 3ada12297649..aad47cd297b2 100644 --- a/apps/server/src/provider/Drivers/ClaudeDriver.ts +++ b/apps/server/src/provider/Drivers/ClaudeDriver.ts @@ -219,9 +219,12 @@ export const ClaudeDriver: ProviderDriver // Only fresh usage earns the TTL; failures, fallbacks and usage-less probes retry. + // Claude answers available-but-null while it holds back usage reads; retry that too. Exit.isSuccess(exit) && exit.value._tag === "fresh" && - exit.value.capabilities.usage !== undefined + exit.value.capabilities.usage !== undefined && + (exit.value.capabilities.usage.rate_limits !== null || + !exit.value.capabilities.usage.rate_limits_available) ? CAPABILITIES_PROBE_TTL : Duration.zero, }, diff --git a/apps/server/src/provider/ProviderInstanceRegistry.test.ts b/apps/server/src/provider/ProviderInstanceRegistry.test.ts index 4faac2c8592b..d785b0bc32a2 100644 --- a/apps/server/src/provider/ProviderInstanceRegistry.test.ts +++ b/apps/server/src/provider/ProviderInstanceRegistry.test.ts @@ -783,7 +783,7 @@ describe("ProviderInstanceRegistry — multi-instance codex slice", () => { ); const makeClaudeCacheFixture = Effect.fnUntraced(function* ( - state: "ready" | "signed-out" | "usage-failed", + state: "ready" | "signed-out" | "usage-failed" | "usage-null", ) { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; @@ -811,6 +811,9 @@ describe("ProviderInstanceRegistry — multi-instance codex slice", () => { sensitive: false, }, ]), + ...(state === "usage-null" + ? [{ name: "T3_CLAUDE_USAGE_FAILS_AS_NULL", value: "1", sensitive: false }] + : []), ], config: makeClaudeConfig({ enabled: true, @@ -829,11 +832,11 @@ describe("ProviderInstanceRegistry — multi-instance codex slice", () => { }; }); - for (const state of ["signed-out", "usage-failed"] as const) { + for (const state of ["signed-out", "usage-failed", "usage-null"] as const) { it.effect.skipIf(windowsHost)( state === "signed-out" ? "shows Claude usage on the first refresh after signing back in" - : "retries Claude usage on the first refresh after a failed usage read", + : `retries Claude usage on the first refresh after a ${state === "usage-null" ? "missing" : "failed"} usage read`, () => Effect.gen(function* () { const fixture = yield* makeClaudeCacheFixture(state); diff --git a/apps/server/src/provider/testing/ProviderInstanceRegistryLive.fixture.mjs b/apps/server/src/provider/testing/ProviderInstanceRegistryLive.fixture.mjs index b996e420f85f..fc585d0da78a 100755 --- a/apps/server/src/provider/testing/ProviderInstanceRegistryLive.fixture.mjs +++ b/apps/server/src/provider/testing/ProviderInstanceRegistryLive.fixture.mjs @@ -41,7 +41,13 @@ lines.on("line", (line) => { process.stdout.write( JSON.stringify({ type: "control_response", - response: { subtype: "error", request_id: message.request_id, error: "usage failed" }, + response: process.env.T3_CLAUDE_USAGE_FAILS_AS_NULL + ? { + subtype: "success", + request_id: message.request_id, + response: { session: {}, rate_limits_available: true, rate_limits: null }, + } + : { subtype: "error", request_id: message.request_id, error: "usage failed" }, }) + "\n", ); return;