April 17, 2026 • Version: 2026.4.9

[Abrechnungs-Cooldown-Überspringung umgeht isBillingErrorMessage()] - Billing Cooldown Skip Path Bypasses isBillingErrorMessage() — Generic Error Shown Instead of Billing Message

Wenn alle Modell-Fallback-Kandidaten aufgrund eines Abrechnungs-Cooldowns übersprungen werden, sehen Benutzer einen generischen „Etwas ist schiefgelaufen“-Fehler anstelle der verwertbaren BILLING_ERROR_USER_MESSAGE, da die vom Cooldown generierte Überspringnachricht keinem Muster in isBillingErrorMessage() entspricht.

🔍 Symptome

Benutzerbezogenes Symptom

Nach dem ersten Abrechnungsfehler mit einem Anthropic OAuth-authentifizierten Konto zeigen alle nachfolgenden Wiederholungsversuche eine generische, nicht handlungsrelevante Fehlermeldung an:

⚠️ Something went wrong while processing your request. Please try again, or use /new to start a fresh session.

Dies wiederholt sich im ~30-Minuten-Takt über Stunden, obwohl die zugrunde liegende Ursache eine erschöpfte Abrechnungsquote aufseiten von Anthropic ist.

Entwickler-sichtbares Symptom (Agent-Protokolle)

Der erste Fehler zeigt korrekt den Abrechnungsfehler:

[agent] embedded run agent end: runId=e8520f5d-... isError=true model=claude-opus-4-6 provider=anthropic error=LLM request rejected: You're out of extra usage. Add more at claude.ai/settings/usage and keep going.
[agent] auth profile failure state updated: runId=e8520f5d-... profile=sha256:154a23a3efe6 provider=anthropic reason=billing window=disabled

Alle nachfolgenden Fehler durchlaufen den Cooldown-Überspringungspfad:

[model-fallback] model fallback decision: decision=skip_candidate requested=anthropic/claude-opus-4-6 candidate=anthropic/claude-opus-4-6 reason=billing next=anthropic/claude-sonnet-4-6 detail=Provider anthropic has billing issue (skipping all models)
[model-fallback] model fallback decision: decision=skip_candidate requested=anthropic/claude-opus-4-6 candidate=anthropic/claude-sonnet-4-6 reason=billing next=none detail=Provider anthropic has billing issue (skipping all models)
Embedded agent failed before reply: All models failed (2): anthropic/claude-opus-4-6: Provider anthropic has billing issue (skipping all models) (billing) | anthropic/claude-sonnet-4-6: Provider anthropic has billing issue (skipping all models) (billing)

Bestätigung durch strukturierte Daten

Der FallbackSummaryError enthält attempt.reason=“billing” für jeden Versuch, aber die isBillingErrorMessage()-Prüfung in agent-runner-execution.ts führt einen String-Abgleich gegen ERROR_PATTERNS.billing in failover-matches.ts durch, der das Muster “has billing issue” nicht enthält.

Häufigkeitsmuster

Der Zyklus wiederholt sich alle 30 Minuten über längere Zeiträume:

2026-04-13T22:41:05 ... Embedded agent failed before reply: All models failed (2): ... (billing) | ... (billing)
2026-04-13T23:11:05 ... Embedded agent failed before reply: All models failed (2): ... (billing) | ... (billing)
2026-04-13T23:41:05 ... Embedded agent failed before reply: All models failed (2): ... (billing) | ... (billing)

🧠 Ursache

Architektur: Zwei Fehlerklassifizierungsstrategien

OpenClaw verwendet zwei unterschiedliche Strategien zur Klassifizierung von Fehlern als abrechnungsbezogen, mit einer Asymmetrie zwischen dem rohen API-Fehlerpfad und dem Cooldown-Überspringungspfad:

  • Roh-API-Fehlerpfad: isBillingErrorMessage(message: string) — Regex/String-Abgleich gegen ERROR_PATTERNS.billing in failover-matches.ts.
  • Rate-Limit-Pfad (bereits korrekt): isPureTransientRateLimitSummary(failure: FallbackSummaryError) — Strukturelle Prüfung gegen attempt.reason === 'rate_limit'.
  • Abrechnungs-Cooldown-Überspringungspfad (fehlerhaft): Keine entsprechende strukturelle Prüfung; verlässt sich ausschließlich auf isBillingErrorMessage()-String-Abgleich, der "has billing issue (skipping all models)" nicht erkennt.

Die Fehlerfolge

  1. Das persönliche „extra usage"-Kontingent des Anthropic OAuth-Nutzers ist erschöpft.
  2. Die erste LLM-Anfrage erhält einen rohen API-Fehler: 400 {"type":"error","error":{"type":"invalid_request_error","message":"You're out of extra usage. Add more at claude.ai/settings/usage and keep going."}}
  3. Die rohe Fehlermeldung entspricht ERROR_PATTERNS.billingauth profile tritt in billing cooldown window=disabled ein.
  4. Nachfolgende Anfragen lösen die Model-Fallback-Überspringungslogik in model-fallback.ts aus.
  5. Jedes Kandidatenmodell wird übersprungen mit Detail: "Provider anthropic has billing issue (skipping all models)".
  6. Ein FallbackSummaryError wird konstruiert mit attempt.reason="billing" für jeden fehlgeschlagenen Versuch.
  7. In agent-runner-execution.ts ruft der Code isBillingErrorMessage(error.message) auf — aber "has billing issue" ist nicht in ERROR_PATTERNS.billing enthalten.
  8. Die Abrechnungsprüfung schlägt fehl, sodass der generische Fallback-Fehlerpfad eingeschlagen wird, was "Something went wrong" erzeugt.

Relevante Codelocations

  • src/core/failover/failover-matches.tsERROR_PATTERNS.billing enthält Muster wie "out of extra usage", "insufficient balance", "billing error", aber **nicht** "has billing issue".
  • src/core/agent-runner-execution.ts — ruft isBillingErrorMessage(message) als alleinige Abrechnungs-Klassifizierungsgate für den Fehlerdarstellungspfad auf.
  • src/core/model-fallback/model-fallback.ts — erzeugt "Provider X has billing issue (skipping all models)"-Meldungen, wenn der Abrechnungs-Cooldown aktiv ist.
  • src/core/failover/failover-matches.tsisPureTransientRateLimitSummary() prüft korrekt attempt.reason === 'rate_limit' als strukturelles Feld und demonstriert das korrekte Muster, das dem Abrechnungspfad fehlt.

Warum der Rate-Limit-Pfad korrekt funktioniert

Der Rate-Limit-Pfad verwendet bereits das strukturelle attempt.reason-Feld:

export function isPureTransientRateLimitSummary(failure: FallbackSummaryError): boolean {
  return failure.attempts.every(a => a.reason === 'rate_limit');
}

Dieser Ansatz ist immun gegen Änderungen der Meldungsstrings, da er das semantische Klassifizierungsfeld inspiziert, nicht die menschenlesbare Meldung.

Warum OAuth das Problem verschärft

Persönliche „extra usage"-Kontingente auf claude.ai sind im Allgemeinen kleiner als organisatorische API-Budgets. OAuth-authentifizierte Konten treffen ihr persönliches Kontingent häufiger als API-Key-authentifizierte Organisationskonten, was diesen Bug zu einem häufigen Benutzererlebnis-Problem für den OAuth-Installationspfad macht.

🛠️ Schritt-für-Schritt-Lösung

Lösungsstrategie

Fügen Sie eine strukturelle Abrechnungsprüfungsfunktion isPureBillingSummary() zu failover-matches.ts hinzu, die das bestehende isPureTransientRateLimitSummary()-Muster spiegelt. Aktualisieren Sie agent-runner-execution.ts, um diese strukturelle Prüfung als primäres Gate für die Abrechnungsklassifizierung zu verwenden, mit String-Abgleich nur als Fallback für Legacy-rohe API-Fehler.

Schritt 1: Strukturelle Abrechnungsprüfung zu failover-matches.ts hinzufügen

Fügen Sie die folgende Funktion zu src/core/failover/failover-matches.ts neben dem bestehenden isPureTransientRateLimitSummary() hinzu:

Vorher:

export function isPureTransientRateLimitSummary(failure: FallbackSummaryError): boolean {
  return failure.attempts.every(a => a.reason === 'rate_limit');
}

Nachher:

export function isPureTransientRateLimitSummary(failure: FallbackSummaryError): boolean {
  return failure.attempts.every(a => a.reason === 'rate_limit');
}

/**
 * Structural check: true when every attempt in the FallbackSummaryError
 * is classified as billing-cooldown. This correctly handles the
 * "Provider X has billing issue (skipping all models)" skip path,
 * which is not matched by isBillingErrorMessage() string patterns.
 */
export function isPureBillingSummary(failure: FallbackSummaryError): boolean {
  return failure.attempts.every(a => a.reason === 'billing');
}

Schritt 2: Abrechnungsklassifizierungs-Gate in agent-runner-execution.ts aktualisieren

Finden Sie die Abrechnungsfehler-Klassifizierungslogik in src/core/agent-runner-execution.ts. Ersetzen Sie die reinen String-Prüfung durch einen strukturell-zuerst-Ansatz:

Vorher:

const isBilling = isBillingErrorMessage(message);

Nachher:

// Prefer structural classification (cooldown skip path) over string matching.
const isBilling = error instanceof FallbackSummaryError
  ? isPureBillingSummary(error)
  : isBillingErrorMessage(message);

Stellen Sie sicher, dass FallbackSummaryError und isPureBillingSummary importiert werden:

import { FallbackSummaryError } from '../model-fallback/types';
import { isPureBillingSummary } from '../failover/failover-matches';

Schritt 3: (Optionale Verbesserung) ERROR_PATTERNS.billing erweitern

Um sicherzustellen, dass rohe API-Fehler durch das Cooldown-Fenster ebenfalls korrekt behandelt werden, erweitern Sie die Abrechnungsmuster in src/core/failover/failover-matches.ts, um die Cooldown-Überspringungsphrase einzuschließen:

Vorher:

export const ERROR_PATTERNS = {
  billing: [
    /out of extra usage/i,
    /insufficient balance/i,
    /billing error/i,
    /api key (has|runs out).*credit/i,
    /add more at.*usage/i,
    /out of credits/i,
  ],
  // ...
};

Nachher:

export const ERROR_PATTERNS = {
  billing: [
    /out of extra usage/i,
    /insufficient balance/i,
    /billing error/i,
    /api key (has|runs out).*credit/i,
    /add more at.*usage/i,
    /out of credits/i,
    /has billing issue \(skipping all models\)/i, // cooldown skip path
  ],
  // ...
};

Dieser dritte Schritt ist defensiv; die Hauptlösung in Schritten 1–2 ist ausreichend, da isPureBillingSummary() short-circuits, bevor der String-Abgleich den FallbackSummaryError-Fall erreicht.

Schritt 4: Rebuild und Deployment

npm run build
# or for Docker deployments:
docker build -t openclaw:fixed .

🧪 Verifizierung

Unit-Test: isPureBillingSummary()

Fügen Sie einen Testfall zu src/core/failover/failover-matches.test.ts hinzu:

import { isPureBillingSummary } from './failover-matches';
import { FallbackSummaryError, FallbackAttempt } from '../model-fallback/types';

describe('isPureBillingSummary', () => {
  it('returns true when all attempts have reason=billing', () => {
    const attempts: FallbackAttempt[] = [
      {
        provider: 'anthropic',
        model: 'claude-opus-4-6',
        reason: 'billing',
        message: 'Provider anthropic has billing issue (skipping all models)',
        durationMs: 0,
        startTime: 0,
        endTime: 0,
      },
      {
        provider: 'anthropic',
        model: 'claude-sonnet-4-6',
        reason: 'billing',
        message: 'Provider anthropic has billing issue (skipping all models)',
        durationMs: 0,
        startTime: 0,
        endTime: 0,
      },
    ];
    const error = new FallbackSummaryError('All models failed', attempts);
    expect(isPureBillingSummary(error)).toBe(true);
  });

  it('returns false when attempts contain mixed reasons', () => {
    const attempts: FallbackAttempt[] = [
      { provider: 'anthropic', model: 'claude-opus-4-6', reason: 'billing', message: '', durationMs: 0, startTime: 0, endTime: 0 },
      { provider: 'anthropic', model: 'claude-sonnet-4-6', reason: 'rate_limit', message: '', durationMs: 0, startTime: 0, endTime: 0 },
    ];
    const error = new FallbackSummaryError('All models failed', attempts);
    expect(isPureBillingSummary(error)).toBe(false);
  });

  it('returns false when no attempt has reason=billing', () => {
    const attempts: FallbackAttempt[] = [
      { provider: 'anthropic', model: 'claude-opus-4-6', reason: 'rate_limit', message: '', durationMs: 0, startTime: 0, endTime: 0 },
    ];
    const error = new FallbackSummaryError('All models failed', attempts);
    expect(isPureBillingSummary(error)).toBe(false);
  });
});

Führen Sie die Testsuite aus:

npm test -- --testPathPattern="failover-matches"
# Expected: isPureBillingSummary tests pass

Integrationstest: Abrechnungs-Cooldown-Fehlerdarstellung

Simulieren Sie ein Abrechnungs-Cooldown-Szenario unter Verwendung eines Testproviders oder eines gemockten AuthProfile:

# Using the OpenClaw CLI test harness (if available):
openclaw test:integration --scenario=billing-cooldown --auth-type=oauth

# Expected output in user-facing message channel:
# "⚠️ 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."
# (i.e., BILLING_ERROR_USER_MESSAGE, not "Something went wrong")

Manuelle Verifizierung: Protokollinspektion

Lösen Sie den Abrechnungs-Cooldown aus und inspizieren Sie die Agent-Protokolle auf die korrigierte Klassifizierung:

# Trigger a billing exhaustion scenario, then observe subsequent failures:
grep -E "(billing|isBilling|Something went wrong)" /var/log/openclaw/agent.log

# Before fix — "Something went wrong" appears repeatedly:
# Embedded agent failed before reply: ... (Something went wrong)
# Embedded agent failed before reply: ... (Something went wrong)

# After fix — BILLING_ERROR_USER_MESSAGE appears:
# [agent] embedded run agent end: ... userMessage=⚠️ API provider returned a billing error...
# Embedded agent failed before reply: ... (billing)

Exit-Code-Verifizierung

# Verify graceful degradation with billing error exit code
openclaw run --prompt="Hello" --model=anthropic/claude-opus-4-6
echo "Exit code: $?"
# Expected: non-zero exit (indicating error state was properly surfaced), NOT a crash

⚠️ Häufige Fehler

  • Nur Erweiterung von ERROR_PATTERNS ohne Hinzufügen von isPureBillingSummary(): Das Hinzufügen von "has billing issue" zu den String-Mustern funktioniert als Workaround, ist aber fragil. Wenn sich das Model-Fallback-Meldungsformat in einer zukünftigen Version ändert (z.B. „Provider X billing cooldown — skipping all models"), bricht das Muster wieder. Der strukturelle isPureBillingSummary()-Ansatz ist resilient gegenüber Meldungsstring-Änderungen.
  • isPureBillingSummary() bedingungslos anwenden: Die Prüfung muss mit instanceof FallbackSummaryError abgesichert werden. Der Aufruf auf einem Raw-String oder anderen Fehlertyp wirft einen TypeError. Der Fallback auf isBillingErrorMessage(message) für Nicht-FallbackSummaryError-Typen bewahrt die Abwärtskompatibilität mit rohen API-Fehlern.
  • OAuth vs. API-Key-Asymmetrie beim Testen: Der Bug manifestiert sich eher bei OAuth-authentifizierten Konten, da persönliche „extra usage"-Kontingente kleiner sind. Das Testen mit Organisations-API-Keys reproduziert das Problem möglicherweise nicht, was zu falscher Zuversicht führt, dass ein Fix funktioniert. Testen Sie immer sowohl OAuth-persönliche-Quota-Szenarien als auch API-Key-Erschöpfungsszenarien.
  • Cooldown-Fenster-Zustandspersistenz: Der Abrechnungs-Cooldown-Zustand bleibt über Neustarts hinweg bestehen, wenn er von einem persistenten Speicher (Redis, SQLite) unterstützt wird. Stellen Sie sicher, dass Testumgebungen den Auth-Profile-Failure-Zustand zwischen den Läufen zurücksetzen, da der Cooldown sonst weiterhin Anfragen blockiert, selbst nach der Behebung des Fehlermeldungspfads.
  • Partielle Model-Fallback-Abdeckung: Wenn nur einige Provider in einer Routing-Kette in den Abrechnungs-Cooldown eintreten, enthält der FallbackSummaryError eine Mischung aus reason: 'billing' und anderen Gründen (z.B. reason: 'timeout'). isPureBillingSummary() gibt in diesem gemischten Fall false zurück. Erwägen Sie das Hinzufügen von isMostlyBillingSummary() als sekundäre Heuristik, wenn gemischte Fehler häufig sind.
  • Docker-Volume-Mount-Timing: Bei Docker-Deployments stellen Sie sicher, dass das neu gebaute Container-Image verwendet wird (docker build, nicht nur docker-compose up -d --build, wenn der Build-Kontext veraltet ist). Ein häufiger Fehler ist das Bearbeiten von Quelldateien und nur das Ausführen von up -d, was das vorhandene Image ohne den Fix verwendet.
  • Protokoll-Ausführlichkeit maskiert das Problem: Wenn LOG_LEVEL=error in der Produktion gesetzt ist, werden die detaillierten [model-fallback] model fallback decision-Zeilen möglicherweise unterdrückt, was es schwieriger macht zu diagnostizieren, ob der Cooldown-Überspringungspfad oder der Raw-Fehlerpfad eingeschlagen wurde. Setzen Sie LOG_LEVEL=debug während der Fehlersuche.

🔗 Zugehörige Fehler

  • FallbackSummaryError mit "Something went wrong"-Meldung — Die generische Fallback-Fehlermeldung, die Benutzer sehen, wenn weder isBillingErrorMessage() noch isPureRateLimitSummary() übereinstimmt. Zeigt eine Klassifizierungslücke in der Fehlerrouting-Logik an.
  • ERROR_PATTERNS.billing Musterschwan — Die Menge der Regex-Muster in failover-matches.ts, die isBillingErrorMessage() für die stringbasierte Abrechnungserkennung verwendet. Das Fehlen der Cooldown-Überspringungsphrase war hier die Grundursache.
  • PR #61608 — partielle Abrechnungsmuster-Behebung — Hat "out of extra usage" nur zu ERROR_PATTERNS.billing für rohe API-Fehler hinzugefügt; hat den cooldown-generierten Überspringungspfad nicht adressiert.
  • Issue #48526 — Zugehörige Abrechnungsfehler-Klassifizierungslücke (möglicherweise frühere Instanz desselben Pattern-Matching- vs. Strukturelle-Klassifizierung-Problems).
  • Issue #64224 — OAuth-Authentifizierung Abrechnungsquota-Erschöpfung verursacht wiederholte Fehler (wahrscheinlich dieselbe Grundursache, unterschiedliche Manifestation).
  • Issue #64308 — Model-Fallback All-Models-Übersprungen-Szenario mit generischer Fehlerausgabe.
  • Issue #62375 — Anthropic-Provider Abrechnungsfehler-Behandlung Edge-Case mit OAuth persönlichen Nutzungskontingenten.
  • isPureTransientRateLimitSummary() — Das korrekte Muster, das der Abrechnungspfad spiegeln sollte. Demonstriert den strukturellen attempt.reason-Inspektionsansatz, der für Rate-Limit-Fehler bereits implementiert wurde.
  • BILLING_ERROR_USER_MESSAGE — Die erwartete benutzerbezogene Meldung, die nicht angezeigt wurde: "⚠️ 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."
  • auth profile failure state: reason=billing window=disabled — Die Auth-Profile-Metadaten, die anzeigen, dass ein Abrechnungs-Cooldown aktiviert wurde, der weitere Versuche für einen definierten Cooldown-Zeitraum verhindert.

Belege & Quellen

Diese Troubleshooting-Anleitung wurde automatisch von der FixClaw Intelligence Pipeline aus Community-Diskussionen synthetisiert.