Skip to content

Billing/quota errors can be misclassified as context overflow #40378

@clawdre-remy

Description

@clawdre-remy

Bug Description

When an LLM provider (e.g., OpenRouter) returns a spend-limit or billing error, it can be misclassified as a "Context overflow" error, showing the user:

Context overflow: prompt too large for the model. Try /reset (or /new) to start a fresh session, or use a larger-context model.

...when the actual problem is that the API key hit its credit/spend limit.

Root Cause

The error classification in the catch block of the agent runner (in compact-*.js, around the runEmbeddedAgent flow) checks isLikelyContextOverflowError() before checking for billing errors. The billing detection (isBillingErrorMessage) only runs on assistant-level errors (structured responses), not on raw thrown exceptions in the catch path.

If the provider's error message contains patterns like "request size exceeds", "413 too large", or similar text that matches the context overflow regex, it gets classified as context overflow even though the underlying cause is a billing/quota limit.

Relevant code paths

  • isLikelyContextOverflowError() in pi-embedded-helpers — broad regex matching on error strings
  • isBillingErrorMessage() in pi-embedded-helpers — checks for "insufficient credits", HTTP 402, "payment required", etc.
  • Catch block in the agent runner (~line 56322 in compact-*.js) — only checks context overflow, compaction failure, role ordering, transient HTTP, and session corruption. No billing check.
  • The assistant-level error path (~line 17961) does correctly detect billing via isBillingAssistantError() and produces the right user-facing message.

Expected Behavior

Billing/quota errors should show the existing billing-specific message:

⚠️ [Provider] returned a billing error — your API key has run out of credits or has an insufficient balance. Check your [provider] billing dashboard and top up or switch to a different API key.

Suggested Fix

In the catch block, check isBillingErrorMessage(message) before isLikelyContextOverflowError(message), since billing is a more specific error class. Something like:

// In the catch block, before the existing context overflow check:
const isBilling = isBillingErrorMessage(message);
const isContextOverflow = !isBilling && isLikelyContextOverflowError(message);

Then add a billing-specific return path similar to the existing context overflow one.

Environment

  • OpenClaw: latest (as of 2026-03-08)
  • Provider: OpenRouter (anthropic/claude-opus-4.6)
  • Trigger: OpenRouter API key spend limit reached

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions