[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=disabledAlle 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 gegenERROR_PATTERNS.billinginfailover-matches.ts. - Rate-Limit-Pfad (bereits korrekt):
isPureTransientRateLimitSummary(failure: FallbackSummaryError)— Strukturelle Prüfung gegenattempt.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
- Das persönliche „extra usage"-Kontingent des Anthropic OAuth-Nutzers ist erschöpft.
- 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."}} - Die rohe Fehlermeldung entspricht
ERROR_PATTERNS.billing→auth profiletritt inbilling cooldown window=disabledein. - Nachfolgende Anfragen lösen die Model-Fallback-Überspringungslogik in
model-fallback.tsaus. - Jedes Kandidatenmodell wird übersprungen mit Detail:
"Provider anthropic has billing issue (skipping all models)". - Ein
FallbackSummaryErrorwird konstruiert mitattempt.reason="billing"für jeden fehlgeschlagenen Versuch. - In
agent-runner-execution.tsruft der CodeisBillingErrorMessage(error.message)auf — aber"has billing issue"ist nicht inERROR_PATTERNS.billingenthalten. - 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.ts—ERROR_PATTERNS.billingenthält Muster wie"out of extra usage","insufficient balance","billing error", aber **nicht**"has billing issue".src/core/agent-runner-execution.ts— ruftisBillingErrorMessage(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.ts—isPureTransientRateLimitSummary()prüft korrektattempt.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 passIntegrationstest: 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 strukturelleisPureBillingSummary()-Ansatz ist resilient gegenüber Meldungsstring-Änderungen. - isPureBillingSummary() bedingungslos anwenden: Die Prüfung muss mit
instanceof FallbackSummaryErrorabgesichert werden. Der Aufruf auf einem Raw-String oder anderen Fehlertyp wirft einen TypeError. Der Fallback aufisBillingErrorMessage(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
FallbackSummaryErroreine Mischung ausreason: 'billing'und anderen Gründen (z.B.reason: 'timeout').isPureBillingSummary()gibt in diesem gemischten Fallfalsezurück. Erwägen Sie das Hinzufügen vonisMostlyBillingSummary()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 nurdocker-compose up -d --build, wenn der Build-Kontext veraltet ist). Ein häufiger Fehler ist das Bearbeiten von Quelldateien und nur das Ausführen vonup -d, was das vorhandene Image ohne den Fix verwendet. - Protokoll-Ausführlichkeit maskiert das Problem: Wenn
LOG_LEVEL=errorin 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 SieLOG_LEVEL=debugwährend der Fehlersuche.
🔗 Zugehörige Fehler
FallbackSummaryErrormit"Something went wrong"-Meldung — Die generische Fallback-Fehlermeldung, die Benutzer sehen, wenn wederisBillingErrorMessage()nochisPureRateLimitSummary()übereinstimmt. Zeigt eine Klassifizierungslücke in der Fehlerrouting-Logik an.ERROR_PATTERNS.billingMusterschwan — Die Menge der Regex-Muster infailover-matches.ts, dieisBillingErrorMessage()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 zuERROR_PATTERNS.billingfü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 strukturellenattempt.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.