Skip to content

Commit 0af55f9

Browse files
fix: check billing errors before surfacing rate-limit message (#79489)
Merged via squash. Prepared head SHA: 2ea757c Co-authored-by: aayushprsingh <172073271+aayushprsingh@users.noreply.github.com> Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com> Reviewed-by: @altaywtf
1 parent a134683 commit 0af55f9

3 files changed

Lines changed: 14 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ Docs: https://docs.openclaw.ai
7272
- Control UI/usage: truncate long context skill, tool, and file names in the usage panel while keeping the full name available on hover. (#42197) Thanks @Rain120.
7373
- Codex: respect explicit `models auth order set` and `config.auth.order` precedence over stale `lastGood` in `/codex account`, and show `no working credential` when every explicit-order profile is ineligible instead of marking a lower-ranked profile as active. Fixes #84386. (#84412) Thanks @openperf.
7474
- Agents: honor `messages.suppressToolErrors` for mutating tool failures so configured chat surfaces do not receive separate warning payloads. (#81561) Thanks @moeedahmed.
75+
- Agents/fallback: surface billing guidance for mixed rate-limit plus billing fallback exhaustion instead of generic failure copy. Fixes #79396. (#79489) Thanks @aayushprsingh.
7576

7677
## 2026.5.19
7778

src/auto-reply/reply/agent-runner-execution.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3549,7 +3549,7 @@ describe("runAgentTurnWithFallback", () => {
35493549
});
35503550
});
35513551

3552-
it("does not show a rate-limit countdown for mixed-cause fallback exhaustion", async () => {
3552+
it("surfaces billing guidance for mixed-cause fallback exhaustion", async () => {
35533553
state.runWithModelFallbackMock.mockRejectedValueOnce(
35543554
Object.assign(
35553555
new Error(
@@ -3593,7 +3593,7 @@ describe("runAgentTurnWithFallback", () => {
35933593

35943594
expect(result.kind).toBe("final");
35953595
if (result.kind === "final") {
3596-
expect(result.payload.text).toBe(GENERIC_RUN_FAILURE_TEXT);
3596+
expect(result.payload.text).toBe("billing");
35973597
expect(result.payload.text).not.toContain("All models failed");
35983598
expect(result.payload.text).not.toContain("402 (billing)");
35993599
expect(result.payload.text).not.toContain("Rate-limited");

src/auto-reply/reply/agent-runner-execution.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,13 @@ function buildRateLimitCooldownMessage(err: unknown): string {
380380
if (codexUsageLimitMessage) {
381381
return codexUsageLimitMessage;
382382
}
383+
if (isFallbackSummaryError(err) && hasBillingAttemptSummary(err)) {
384+
return BILLING_ERROR_USER_MESSAGE;
385+
}
386+
const message = formatErrorMessage(err);
387+
if (isBillingErrorMessage(message)) {
388+
return BILLING_ERROR_USER_MESSAGE;
389+
}
383390
if (!isFallbackSummaryError(err)) {
384391
return "⚠️ All models are temporarily rate-limited. Please try again in a few minutes.";
385392
}
@@ -448,11 +455,11 @@ function isPureTransientRateLimitSummary(err: unknown): boolean {
448455
);
449456
}
450457

451-
function isPureBillingSummary(err: unknown): boolean {
458+
function hasBillingAttemptSummary(err: unknown): boolean {
452459
return (
453460
isFallbackSummaryError(err) &&
454461
err.attempts.length > 0 &&
455-
err.attempts.every((attempt) => attempt.reason === "billing")
462+
err.attempts.some((attempt) => attempt.reason === "billing")
456463
);
457464
}
458465

@@ -625,7 +632,7 @@ export function buildKnownAgentRunFailureReplyPayload(params: {
625632
const message = formatErrorMessage(params.err);
626633
const isFallbackSummary = isFallbackSummaryError(params.err);
627634
const isBilling = isFallbackSummary
628-
? isPureBillingSummary(params.err)
635+
? hasBillingAttemptSummary(params.err)
629636
: isBillingErrorMessage(message);
630637
if (isBilling) {
631638
return markAgentRunFailureReplyPayload({
@@ -2263,7 +2270,7 @@ export async function runAgentTurnWithFallback(params: {
22632270
}
22642271
const message = formatErrorMessage(err);
22652272
const isBilling = isFallbackSummaryError(err)
2266-
? isPureBillingSummary(err)
2273+
? hasBillingAttemptSummary(err)
22672274
: isBillingErrorMessage(message);
22682275
const isContextOverflow = !isBilling && isLikelyContextOverflowError(message);
22692276
const isCompactionFailure = !isBilling && isCompactionFailureError(message);

0 commit comments

Comments
 (0)