fix(errors): show clear billing error instead of cryptic API response (#8391)

* fix(errors): return clear billing error message instead of cryptic raw error (#8136)

When an LLM API provider returns a credit/billing-related error (HTTP 402,
insufficient credits, low balance, etc.), OpenClaw now shows a clear,
actionable message instead of passing through the raw/cryptic error text:

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

Changes:
- formatAssistantErrorText: detect billing errors via isBillingErrorMessage()
  and return a user-friendly message (placed before the generic HTTP/JSON
  error fallthrough)
- sanitizeUserFacingText: same billing detection for the sanitization path
- pi-embedded-runner/run.ts: add billingFailure detection in the profile
  exhaustion fallback, so the FailoverError message is billing-specific
- Added 3 new tests for credit balance, HTTP 402, and insufficient credits

* fix: extract billing error message to shared constant
This commit is contained in:
Glucksberg
2026-02-05 17:58:43 -04:00
committed by GitHub
parent 4e1a7cd60c
commit d4c560853c
4 changed files with 36 additions and 4 deletions

View File

@@ -29,9 +29,11 @@ import {
import { normalizeProviderId } from "../model-selection.js";
import { ensureOpenClawModelsJson } from "../models-config.js";
import {
BILLING_ERROR_USER_MESSAGE,
classifyFailoverReason,
formatAssistantErrorText,
isAuthAssistantError,
isBillingAssistantError,
isCompactionFailureError,
isContextOverflowError,
isFailoverAssistantError,
@@ -549,6 +551,7 @@ export async function runEmbeddedPiAgent(
const authFailure = isAuthAssistantError(lastAssistant);
const rateLimitFailure = isRateLimitAssistantError(lastAssistant);
const billingFailure = isBillingAssistantError(lastAssistant);
const failoverFailure = isFailoverAssistantError(lastAssistant);
const assistantFailoverReason = classifyFailoverReason(lastAssistant?.errorMessage ?? "");
const cloudCodeAssistFormatError = attempt.cloudCodeAssistFormatError;
@@ -620,9 +623,11 @@ export async function runEmbeddedPiAgent(
? "LLM request timed out."
: rateLimitFailure
? "LLM request rate limited."
: authFailure
? "LLM request unauthorized."
: "LLM request failed.");
: billingFailure
? BILLING_ERROR_USER_MESSAGE
: authFailure
? "LLM request unauthorized."
: "LLM request failed.");
const status =
resolveFailoverStatus(assistantFailoverReason ?? "unknown") ??
(isTimeoutErrorMessage(message) ? 408 : undefined);