Post-Mortem: Claude Max + OpenClaw Billing Errors from Stale OAuth and Isolated Cron Jobs

✍️ OpenClawRadar📅 Published: May 12, 2026🔗 Source
Post-Mortem: Claude Max + OpenClaw Billing Errors from Stale OAuth and Isolated Cron Jobs
Ad

A self-hosted OpenClaw setup with Claude Max started returning billing error — API key has run out of credits despite being well under quota. Two days of debugging revealed two root causes and a kill chain that makes the failure appear random.

Root Cause 1: Stale OAuth Token Poisons Whole Provider

The auth-profiles.json had two entries: a valid anthropic:claude-cli OAuth profile and an anthropic:manual profile with an sk-ant-oat01-... token. When the manual token expired, OpenClaw didn't just fail that profile — it classified it as a provider-level billing failure and blacklisted the entire anthropic provider, skipping every model including the healthy OAuth profile. Logs showed:

reason: "billing" errorPreview: "Provider anthropic has billing issue (skipping all models)" chain_exhausted

The healthy OAuth profile with a valid refresh token was never tried.

Fix: Remove anthropic:manual entirely from auth-profiles.json and openclaw.json. Keep only anthropic:claude-cli.

Root Cause 2: Isolated Cron Jobs Hit a Separate Billing Bucket

OpenClaw has two execution paths:

  • Main sessions — runs /usr/bin/claude binary, billed to Max/Pro subscription ✅
  • Isolated/embedded runs — direct HTTP to Anthropic API, billed to Extra Usage bucket

Cron jobs with sessionTarget: isolated spin up a standalone embedded agent that calls the Anthropic API directly via HTTP — without the Claude Code headers the CLI sends. Anthropic routes it to the Extra Usage bucket, a completely separate quota. With Extra Usage off, every isolated cron run returns a 400. OpenClaw then worsens it: one billing error sets disabledUntil ~24 hours forward in auth-state.json, locking out all requests — including normal chat — until cooldown fires. The lockout survives gateway restarts.

Ad

The Full Kill Chain

  1. Gateway restart → missed cron job queues for catch-up
  2. Isolated agent fires via embedded runner → direct HTTP call to Anthropic API (no CLI headers)
  3. Extra Usage bucket → 400 error
  4. OpenClaw locks auth profile for ~24h → all requests blocked including normal chat

Fixes

  1. Clear the billing lockout immediately:
    python3 -c "
    import json
    with open('/home/USER/.openclaw/agents/main/agent/auth-state.json') as f:
        d = json.load(f)
    if 'usageStats' in d:
        for profile in d['usageStats']:
            d['usageStats'][profile].pop('disabledUntil', None)
            d['usageStats'][profile].pop('failureCounts', None)
            d['usageStats'][profile].pop('errorCount', None)
            d['usageStats'][profile].pop('disabledReason', None)
            d['usageStats'][profile].pop('lastFailureAt', None)
    with open('/home/USER/.openclaw/agents/main/agent/auth-state.json', 'w') as f:
        json.dump(d, f, indent=2)
    print('Cleared.')
    "
    openclaw gateway restart
  2. Move all cron jobs from isolated to main:
    python3 -c "
    import json
    with open('/home/USER/.openclaw/cron/jobs.json') as f:
        d = json.load(f)
    jobs = d if isinstance(d, list) else d.get('jobs', [])
    for j in jobs:
        if j.get('sessionTarget') == 'isolated':
            print(f'Fixing: {j["name"]}')
            j['sessionTarget'] = 'main'
    with open('/home/USER/.openclaw/cron/jobs.json', 'w') as f:
        json.dump(d, f, indent=2)
    print('Done.')
    "

Who it's for: Anyone self-hosting OpenClaw with Claude Max or Pro who sees random billing errors despite being under quota.

📖 Read the full source: r/openclaw

Ad

👀 See Also