Skip to content

Commit 4473242

Browse files
fix: use unknown instead of rate_limit as default cooldown reason (openclaw#42911)
Merged via squash. Prepared head SHA: bebf670 Co-authored-by: VibhorGautam <[email protected]> Co-authored-by: altaywtf <[email protected]> Reviewed-by: @altaywtf
1 parent 60aed95 commit 4473242

File tree

7 files changed

+25
-13
lines changed

7 files changed

+25
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ Docs: https://docs.openclaw.ai
105105
- Telegram/final preview cleanup follow-up: clear stale cleanup-retain state only for transient preview finals so archived-preview retains no longer leave a stale partial bubble beside a later fallback-sent final. (#41763) Thanks @obviyus.
106106
- Signal/config schema: accept `channels.signal.accountUuid` in strict config validation so loop-protection configs no longer fail with an unrecognized-key error. (#35578) Thanks @ingyukoh.
107107
- Telegram/config schema: accept `channels.telegram.actions.editMessage` and `createForumTopic` in strict config validation so existing Telegram action toggles no longer fail as unrecognized keys. (#35498) Thanks @ingyukoh.
108+
- Agents/cooldowns: default cooldown windows with no recorded failure history to `unknown` instead of `rate_limit`, avoiding false API rate-limit warnings while preserving cooldown recovery probes. (#42911) Thanks @VibhorGautam.
108109

109110
## 2026.3.8
110111

src/agents/auth-profiles/usage.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ describe("resolveProfilesUnavailableReason", () => {
207207
).toBe("overloaded");
208208
});
209209

210-
it("falls back to rate_limit when active cooldown has no reason history", () => {
210+
it("falls back to unknown when active cooldown has no reason history", () => {
211211
const now = Date.now();
212212
const store = makeStore({
213213
"anthropic:default": {
@@ -221,7 +221,7 @@ describe("resolveProfilesUnavailableReason", () => {
221221
profileIds: ["anthropic:default"],
222222
now,
223223
}),
224-
).toBe("rate_limit");
224+
).toBe("unknown");
225225
});
226226

227227
it("ignores expired windows and returns null when no profile is actively unavailable", () => {

src/agents/auth-profiles/usage.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,11 @@ export function resolveProfilesUnavailableReason(params: {
110110
recordedReason = true;
111111
}
112112
if (!recordedReason) {
113-
addScore("rate_limit", 1);
113+
// No failure counts recorded for this cooldown window. Previously this
114+
// defaulted to "rate_limit", which caused false "rate limit reached"
115+
// warnings when the actual reason was unknown (e.g. transient network
116+
// blip or server error without a classified failure count).
117+
addScore("unknown", 1);
114118
}
115119
}
116120

src/agents/model-fallback.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@ describe("runWithModelFallback", () => {
555555
usageStat: {
556556
cooldownUntil: Date.now() + 5 * 60_000,
557557
},
558-
expectedReason: "rate_limit",
558+
expectedReason: "unknown",
559559
});
560560
});
561561

src/agents/model-fallback.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ function resolveCooldownDecision(params: {
449449
store: params.authStore,
450450
profileIds: params.profileIds,
451451
now: params.now,
452-
}) ?? "rate_limit";
452+
}) ?? "unknown";
453453
const isPersistentAuthIssue = inferredReason === "auth" || inferredReason === "auth_permanent";
454454
if (isPersistentAuthIssue) {
455455
return {
@@ -483,7 +483,10 @@ function resolveCooldownDecision(params: {
483483
// limits, which are often model-scoped and can recover on a sibling model.
484484
const shouldAttemptDespiteCooldown =
485485
(params.isPrimary && (!params.requestedModel || shouldProbe)) ||
486-
(!params.isPrimary && (inferredReason === "rate_limit" || inferredReason === "overloaded"));
486+
(!params.isPrimary &&
487+
(inferredReason === "rate_limit" ||
488+
inferredReason === "overloaded" ||
489+
inferredReason === "unknown"));
487490
if (!shouldAttemptDespiteCooldown) {
488491
return {
489492
type: "skip",
@@ -588,13 +591,16 @@ export async function runWithModelFallback<T>(params: {
588591
if (
589592
decision.reason === "rate_limit" ||
590593
decision.reason === "overloaded" ||
591-
decision.reason === "billing"
594+
decision.reason === "billing" ||
595+
decision.reason === "unknown"
592596
) {
593597
// Probe at most once per provider per fallback run when all profiles
594598
// are cooldowned. Re-probing every same-provider candidate can stall
595599
// cross-provider fallback on providers with long internal retries.
596600
const isTransientCooldownReason =
597-
decision.reason === "rate_limit" || decision.reason === "overloaded";
601+
decision.reason === "rate_limit" ||
602+
decision.reason === "overloaded" ||
603+
decision.reason === "unknown";
598604
if (isTransientCooldownReason && cooldownProbeUsedProviders.has(candidate.provider)) {
599605
const error = `Provider ${candidate.provider} is in cooldown (probe already attempted this run)`;
600606
attempts.push({

src/agents/pi-embedded-runner.run-embedded-pi-agent.auth-profile-rotation.e2e.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -981,7 +981,7 @@ describe("runEmbeddedPiAgent auth profile rotation", () => {
981981
}),
982982
).rejects.toMatchObject({
983983
name: "FailoverError",
984-
reason: "rate_limit",
984+
reason: "unknown",
985985
provider: "openai",
986986
model: "mock-1",
987987
});
@@ -1153,7 +1153,7 @@ describe("runEmbeddedPiAgent auth profile rotation", () => {
11531153
}),
11541154
).rejects.toMatchObject({
11551155
name: "FailoverError",
1156-
reason: "rate_limit",
1156+
reason: "unknown",
11571157
provider: "openai",
11581158
model: "mock-1",
11591159
});

src/agents/pi-embedded-runner/run.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ export async function runEmbeddedPiAgent(
553553
resolveProfilesUnavailableReason({
554554
store: authStore,
555555
profileIds,
556-
}) ?? "rate_limit"
556+
}) ?? "unknown"
557557
);
558558
}
559559
const classified = classifyFailoverReason(params.message);
@@ -669,14 +669,15 @@ export async function runEmbeddedPiAgent(
669669
? (resolveProfilesUnavailableReason({
670670
store: authStore,
671671
profileIds: autoProfileCandidates,
672-
}) ?? "rate_limit")
672+
}) ?? "unknown")
673673
: null;
674674
const allowTransientCooldownProbe =
675675
params.allowTransientCooldownProbe === true &&
676676
allAutoProfilesInCooldown &&
677677
(unavailableReason === "rate_limit" ||
678678
unavailableReason === "overloaded" ||
679-
unavailableReason === "billing");
679+
unavailableReason === "billing" ||
680+
unavailableReason === "unknown");
680681
let didTransientCooldownProbe = false;
681682

682683
while (profileIndex < profileCandidates.length) {

0 commit comments

Comments
 (0)