Summary
OpenClaw's auth-state usage tracker mis-categorizes a successful Claude CLI response carrying a 7-day-cap warning as a billing failure. The response is 200 OK with content; only the embedded rate_limit_event carries a soft signal:
{
"type":"rate_limit_event",
"rate_limit_info":{
"rateLimitType":"seven_day",
"utilization":0.87,
"status":"allowed_warning",
"surpassedThreshold":0.75,
"isUsingOverage":false,
"resetsAt":1778835600
}
}
status: "allowed_warning" explicitly indicates the request was served and the agent is just approaching the cap, not denied. But OpenClaw classifies this as a billing failure, marks the anthropic:claude-cli profile disabledReason: billing, sets disabledUntil to now + 5 hours (per auth.cooldowns.billingBackoffHours default), and starts falling through to the next model in agents.defaults.model.fallbacks (e.g. openrouter/deepseek/deepseek-v4-pro) for the entire 5-hour window.
For users on Pro Max plans this means: once they cross the 75% utilization threshold, every subsequent claude-cli call appears to spend ~5 hours marked disabled, even though the plan is still serving requests fine. The user-visible effect is "OpenClaw keeps falling back to DeepSeek after 1-2 messages" despite no actual rate limit being hit.
Empirical confirmation
Tonight (2026-05-11 ~00:30 UTC) on Pro Max:
- Anthropic settings page: All models 87% used, weekly limits, allowed-warning (yellow bar, not red). Reset Fri 5 AM.
- Standalone
claude -p 'hi' --output-format stream-json --include-partial-messages --verbose --model opus: returns normally ({"type":"result","subtype":"success","is_error":false,"result":"Hello!"}). Cost $0.12. Rate-limit event in the stream carries status: "allowed_warning", utilization: 0.87.
- OpenClaw
auth-state.json after that turn: usageStats.anthropic:claude-cli.disabledReason: "billing", disabledUntil = now + 5h, failureCounts: {"billing": 1}.
The CLI is succeeding; OpenClaw is recording it as a billing failure.
Suspected root cause
The classifier almost certainly doesn't read the rate_limit_event.status field. It probably matches on the presence of rate_limit_event + surpassedThreshold > 0 (or similar heuristic) and treats it as a usage/billing failure of some kind. The classification should differentiate:
rate_limit_info.status |
Correct classification |
"allowed" |
Success. No failure. |
"allowed_warning" |
Success with warning. No failure. Log a UI notice if desired, but do not record in usageStats.failureCounts. |
"denied" / explicit cap-reached |
rate_limit reason. Short backoff. |
Explicit payment_required 402 / billing rejection text |
billing reason. Long backoff. |
Current behavior collapses cases 2-4 into billing, which is the most punishing backoff bucket (5 hours base, 24 hours cap per auth.cooldowns.billingMaxHours).
Suggested fix
In the CLI-shim output parser (dist/agent-runner.runtime-*.js area), inspect any emitted rate_limit_event payloads:
- If
status === "allowed" or "allowed_warning": ignore (or surface as a soft notice, not a failure). Don't write to failureCounts/disabledUntil.
- If
status === "denied": classify as rate_limit, use the rate-limit backoff path, not billing.
- Reserve
billing classification for explicit Anthropic billing/credit responses (payment_required, insufficient_credits, etc.) — Claude Pro Max plan-covered Opus/Sonnet calls cannot fail for billing reasons in the API-key sense; their only failure modes are rate-limit / denial.
Operator-side workaround applied tonight
Doesn't fix the classification, but limits the blast radius. Added to openclaw.json:
{
"auth": {
"cooldowns": {
"billingBackoffHoursByProvider": {
"claude-cli": 0.05
}
}
}
}
This caps the claude-cli billing backoff at ~3 minutes instead of 5 hours, so the misclassification still happens but recovers quickly. Important: scoped to claude-cli only — the global default stays at 5h, and the anthropic direct-API provider keeps the long backoff (which is correct, since direct-API billing failures on that path are real expensive issues).
Doesn't replace the actual fix — the misclassification produces noise in usageStats.failureCounts.billing that pollutes other metrics — but it stops being a user-visible problem.
Cross-references
- Operator memory note documenting the two-Anthropic-auth distinction (CLI-shim plan-covered vs direct-API paid overage): the misclassification specifically only matters for the plan-covered claude-cli path; the direct-API path correctly should stay in long backoff on real billing failures.
- Related but distinct:
openclaw/openclaw#80065 requested UI exposure of auth.cooldowns.*ProfileRotations knobs. This issue is about the classification logic itself, separate from UI surfacing.
Environment
- OpenClaw 2026.5.7 (gitSha
b8fe34a)
- Claude CLI:
2.1.126 (Claude Code)
- User plan: Claude Pro Max (20x)
- Trigger: 7-day usage >75%, plan reports
allowed_warning, CLI continues to serve responses normally
Summary
OpenClaw's auth-state usage tracker mis-categorizes a successful Claude CLI response carrying a 7-day-cap warning as a
billingfailure. The response is200 OKwith content; only the embeddedrate_limit_eventcarries a soft signal:{ "type":"rate_limit_event", "rate_limit_info":{ "rateLimitType":"seven_day", "utilization":0.87, "status":"allowed_warning", "surpassedThreshold":0.75, "isUsingOverage":false, "resetsAt":1778835600 } }status: "allowed_warning"explicitly indicates the request was served and the agent is just approaching the cap, not denied. But OpenClaw classifies this as abillingfailure, marks theanthropic:claude-cliprofiledisabledReason: billing, setsdisabledUntilto now + 5 hours (perauth.cooldowns.billingBackoffHoursdefault), and starts falling through to the next model inagents.defaults.model.fallbacks(e.g.openrouter/deepseek/deepseek-v4-pro) for the entire 5-hour window.For users on Pro Max plans this means: once they cross the 75% utilization threshold, every subsequent claude-cli call appears to spend ~5 hours marked disabled, even though the plan is still serving requests fine. The user-visible effect is "OpenClaw keeps falling back to DeepSeek after 1-2 messages" despite no actual rate limit being hit.
Empirical confirmation
Tonight (2026-05-11 ~00:30 UTC) on Pro Max:
claude -p 'hi' --output-format stream-json --include-partial-messages --verbose --model opus: returns normally ({"type":"result","subtype":"success","is_error":false,"result":"Hello!"}). Cost $0.12. Rate-limit event in the stream carriesstatus: "allowed_warning",utilization: 0.87.auth-state.jsonafter that turn:usageStats.anthropic:claude-cli.disabledReason: "billing",disabledUntil=now + 5h,failureCounts: {"billing": 1}.The CLI is succeeding; OpenClaw is recording it as a billing failure.
Suspected root cause
The classifier almost certainly doesn't read the
rate_limit_event.statusfield. It probably matches on the presence ofrate_limit_event+surpassedThreshold > 0(or similar heuristic) and treats it as a usage/billing failure of some kind. The classification should differentiate:rate_limit_info.status"allowed""allowed_warning"usageStats.failureCounts."denied"/ explicit cap-reachedrate_limitreason. Short backoff.payment_required402 / billing rejection textbillingreason. Long backoff.Current behavior collapses cases 2-4 into
billing, which is the most punishing backoff bucket (5 hours base, 24 hours cap perauth.cooldowns.billingMaxHours).Suggested fix
In the CLI-shim output parser (
dist/agent-runner.runtime-*.jsarea), inspect any emittedrate_limit_eventpayloads:status === "allowed"or"allowed_warning": ignore (or surface as a soft notice, not a failure). Don't write tofailureCounts/disabledUntil.status === "denied": classify asrate_limit, use the rate-limit backoff path, not billing.billingclassification for explicit Anthropic billing/credit responses (payment_required,insufficient_credits, etc.) — Claude Pro Max plan-covered Opus/Sonnet calls cannot fail for billing reasons in the API-key sense; their only failure modes are rate-limit / denial.Operator-side workaround applied tonight
Doesn't fix the classification, but limits the blast radius. Added to
openclaw.json:{ "auth": { "cooldowns": { "billingBackoffHoursByProvider": { "claude-cli": 0.05 } } } }This caps the
claude-clibilling backoff at ~3 minutes instead of 5 hours, so the misclassification still happens but recovers quickly. Important: scoped toclaude-clionly — the global default stays at 5h, and theanthropicdirect-API provider keeps the long backoff (which is correct, since direct-API billing failures on that path are real expensive issues).Doesn't replace the actual fix — the misclassification produces noise in
usageStats.failureCounts.billingthat pollutes other metrics — but it stops being a user-visible problem.Cross-references
openclaw/openclaw#80065requested UI exposure ofauth.cooldowns.*ProfileRotationsknobs. This issue is about the classification logic itself, separate from UI surfacing.Environment
b8fe34a)2.1.126 (Claude Code)allowed_warning, CLI continues to serve responses normally