Summary
Auth profile cooldown is tracked per-profile (usageStats[profileId].cooldownUntil) with no model dimension. When a profile hits a rate limit on Model A, its cooldown blocks it from being tried on Model B — even though Model B may have completely independent rate limits and available capacity.
Steps to reproduce
- Configure 3 auth profiles for the same provider (e.g., Anthropic):
default (Max Pro), tst (Team), api (API console key)
- Configure model fallbacks:
opus (primary) → sonnet → haiku
- Exhaust rate limits on
opus for profiles default and tst (but default still has sonnet capacity)
- Have the
api profile at its monthly spending limit
- Send a message
Expected behavior
The model fallback chain should try default + sonnet — a working combination. The default profile's opus rate limit should not prevent it from being used with sonnet.
Actual behavior
All models failed (3): anthropic/claude-opus-4-6: LLM request rejected: You have reached
your specified API usage limits. (rate_limit) | anthropic/claude-sonnet-4-6: LLM request
rejected: You have reached your specified API usage limits. (rate_limit) |
anthropic/claude-haiku-4-5: LLM request rejected: You have reached your specified API
usage limits. (rate_limit)
The default profile was put into cooldown by its opus rate limit and never tried for sonnet.
Root cause
markAuthProfileFailure() in src/agents/auth-profiles/usage.ts sets cooldownUntil per-profile with no model context
isProfileInCooldown() returns true for the entire profile regardless of which model caused the cooldown
- The embedded runner's profile-skip loop (
src/agents/pi-embedded-runner/run.ts ~L640) checks isProfileInCooldown(authStore, candidate) without model awareness
- The model-fallback outer loop (
src/agents/model-fallback.ts) has a cooldownProbeUsedProviders partial mitigation, but it only probes once per provider per run — insufficient when different models have independent rate limit pools
Impact
This is most impactful for:
The configured fallback chain provides zero resilience when all profiles get rate-limited on the primary model, even when fallback models have available capacity on one or more profiles.
Related issues and PRs
Suggested fix
Add model context to cooldown tracking so rate-limit cooldowns are scoped to the model that caused them:
- Add
cooldownModel?: string to ProfileUsageStats
- In
markAuthProfileFailure, store the model that triggered the failure
- In
isProfileInCooldown, add an optional forModel parameter — when the cooldown reason is rate_limit and cooldownModel differs from forModel, treat the profile as available
- Auth/billing failures remain profile-wide (not model-scoped) since they affect all models
This is surgical, backward-compatible (new field is optional), and preserves existing behavior for non-rate-limit failures.
OpenClaw version
2026.3.2
Operating system
Ubuntu 24.04 (Docker)
Install method
docker
Summary
Auth profile cooldown is tracked per-profile (
usageStats[profileId].cooldownUntil) with no model dimension. When a profile hits a rate limit on Model A, its cooldown blocks it from being tried on Model B — even though Model B may have completely independent rate limits and available capacity.Steps to reproduce
default(Max Pro),tst(Team),api(API console key)opus(primary) →sonnet→haikuopusfor profilesdefaultandtst(butdefaultstill hassonnetcapacity)apiprofile at its monthly spending limitExpected behavior
The model fallback chain should try
default+sonnet— a working combination. Thedefaultprofile's opus rate limit should not prevent it from being used with sonnet.Actual behavior
The
defaultprofile was put into cooldown by its opus rate limit and never tried for sonnet.Root cause
markAuthProfileFailure()insrc/agents/auth-profiles/usage.tssetscooldownUntilper-profile with no model contextisProfileInCooldown()returnstruefor the entire profile regardless of which model caused the cooldownsrc/agents/pi-embedded-runner/run.ts~L640) checksisProfileInCooldown(authStore, candidate)without model awarenesssrc/agents/model-fallback.ts) has acooldownProbeUsedProviderspartial mitigation, but it only probes once per provider per run — insufficient when different models have independent rate limit poolsImpact
This is most impactful for:
The configured fallback chain provides zero resilience when all profiles get rate-limited on the primary model, even when fallback models have available capacity on one or more profiles.
Related issues and PRs
Suggested fix
Add model context to cooldown tracking so rate-limit cooldowns are scoped to the model that caused them:
cooldownModel?: stringtoProfileUsageStatsmarkAuthProfileFailure, store the model that triggered the failureisProfileInCooldown, add an optionalforModelparameter — when the cooldown reason israte_limitandcooldownModeldiffers fromforModel, treat the profile as availableThis is surgical, backward-compatible (new field is optional), and preserves existing behavior for non-rate-limit failures.
OpenClaw version
2026.3.2
Operating system
Ubuntu 24.04 (Docker)
Install method
docker