From 8ea6f5206c8f4237525f1708e54753ad40c0ebf7 Mon Sep 17 00:00:00 2001 From: kiranvk2011 Date: Wed, 18 Mar 2026 15:36:32 +0000 Subject: [PATCH] fix: update cooldownReason on active-window failures + clear model scope for non-rate-limit --- src/agents/auth-profiles/usage.test.ts | 47 ++++++++++++++++++++++++++ src/agents/auth-profiles/usage.ts | 10 +++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/agents/auth-profiles/usage.test.ts b/src/agents/auth-profiles/usage.test.ts index 8d633a7d89b..69d3d4ca200 100644 --- a/src/agents/auth-profiles/usage.test.ts +++ b/src/agents/auth-profiles/usage.test.ts @@ -796,4 +796,51 @@ describe("markAuthProfileFailure — per-model cooldown metadata", () => { const stats = store.usageStats?.["github-copilot:github"]; expect(stats?.cooldownModel).toBe("claude-sonnet-4.6"); }); + + it("updates cooldownReason when auth failure occurs during active rate_limit window", async () => { + const now = 1_000_000; + const store = makeStoreWithCopilot({ + "github-copilot:github": { + cooldownUntil: now + 30_000, + cooldownReason: "rate_limit", + cooldownModel: "claude-sonnet-4.6", + errorCount: 1, + lastFailureAt: now - 1000, + }, + }); + await markAuthProfileFailure({ + store, + profileId: "github-copilot:github", + reason: "auth", + modelId: "claude-opus-4.6", + }); + const stats = store.usageStats?.["github-copilot:github"]; + // Reason should update to the new failure type, not stay as rate_limit + expect(stats?.cooldownReason).toBe("auth"); + // Model scope should be cleared — auth failures are profile-wide + expect(stats?.cooldownModel).toBeUndefined(); + }); + + it("clears cooldownModel when non-rate_limit failure hits same model during active window", async () => { + const now = 1_000_000; + const store = makeStoreWithCopilot({ + "github-copilot:github": { + cooldownUntil: now + 30_000, + cooldownReason: "rate_limit", + cooldownModel: "claude-sonnet-4.6", + errorCount: 1, + lastFailureAt: now - 1000, + }, + }); + await markAuthProfileFailure({ + store, + profileId: "github-copilot:github", + reason: "auth", + modelId: "claude-sonnet-4.6", + }); + const stats = store.usageStats?.["github-copilot:github"]; + // Even same-model auth failure should clear model scope (auth is profile-wide) + expect(stats?.cooldownReason).toBe("auth"); + expect(stats?.cooldownModel).toBeUndefined(); + }); }); diff --git a/src/agents/auth-profiles/usage.ts b/src/agents/auth-profiles/usage.ts index f9b7cfc975f..84f9bdaa83b 100644 --- a/src/agents/auth-profiles/usage.ts +++ b/src/agents/auth-profiles/usage.ts @@ -479,7 +479,11 @@ function computeNextProfileUsageStats(params: { typeof params.existing.cooldownUntil === "number" && params.existing.cooldownUntil > params.now; if (existingCooldownActive) { - updatedStats.cooldownReason = params.existing.cooldownReason; + // Always use the latest failure reason so that downstream consumers + // (e.g. isProfileInCooldown model-bypass) see the most recent signal. + // A non-rate_limit failure (auth, billing, …) is profile-wide, so + // upgrading from rate_limit → auth correctly blocks all models. + updatedStats.cooldownReason = params.reason; // If a different model fails during an active window, widen the scope // to all models (undefined) so neither model bypasses the cooldown. if ( @@ -488,6 +492,10 @@ function computeNextProfileUsageStats(params: { params.existing.cooldownModel !== params.modelId ) { updatedStats.cooldownModel = undefined; + } else if (params.reason !== "rate_limit") { + // Non-rate-limit failures are profile-wide — clear model scope even + // when the same model fails, so that no model can bypass. + updatedStats.cooldownModel = undefined; } else { updatedStats.cooldownModel = params.existing.cooldownModel; }