Skip to content

Auth handler classifies Claude Pro Max 7-day-cap allowed_warning as billing failure → 5-hour false cooldown → unwanted fallback to OpenRouter #80514

Description

@wherewolf87

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.impact:auth-providerAuth, provider routing, model choice, or SecretRef resolution may break.staleMarked as stale due to inactivity

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions