fix(cli): make FreeUsageLimitError non-retryable to prevent unrecoverable backoff loop (#7809)

* fix(cli): make FreeUsageLimitError non-retryable to prevent unrecoverable backoff loop

When a free model hits its usage cap, the retry loop would endlessly
retry with the same stale model reference. Switching models in the chat
selector could not break the loop because the processor captures the
model once at creation time. Making FreeUsageLimitError non-retryable
surfaces the error immediately so users can switch models and continue.

* chore(kilo-docs): update source links after removing FreeUsageLimitError URL
This commit is contained in:
Marius
2026-03-27 17:19:30 +01:00
committed by GitHub
parent b91627e1ff
commit 9cf83d8122
3 changed files with 15 additions and 3 deletions
-1
View File
@@ -7,7 +7,6 @@
<!-- packages/opencode/src/cli/cmd/github.ts -->
- <https://app.kilo.ai>
<!-- packages/opencode/src/kilocode/kilo-commands.tsx -->
<!-- packages/opencode/src/session/retry.ts -->
- <https://app.kilo.ai/config.json>
<!-- packages/opencode/src/config/config.ts -->
- <https://app.kilo.ai/credits>
+5 -2
View File
@@ -67,8 +67,11 @@ export namespace SessionRetry {
if (isKiloError(error)) return undefined
// kilocode_change end
if (!error.data.isRetryable) return undefined
if (error.data.responseBody?.includes("FreeUsageLimitError"))
return `Free usage exceeded, add credits https://app.kilo.ai` // kilocode_change
// kilocode_change start - FreeUsageLimitError is not retryable: retrying the same
// capped model is futile and the backoff loop cannot be broken by switching
// models in the chat selector (the retry loop holds a stale model ref).
if (error.data.responseBody?.includes("FreeUsageLimitError")) return undefined
// kilocode_change end
return error.data.message.includes("Overloaded") ? "Provider is overloaded" : error.data.message
}
@@ -122,6 +122,16 @@ describe("session.retry.retryable", () => {
expect(SessionRetry.retryable(error)).toBeUndefined()
})
test("does not retry FreeUsageLimitError", () => {
const error = new MessageV2.APIError({
message: "rate limit exceeded",
isRetryable: true,
responseBody: '{"error":{"code":"FreeUsageLimitError"}}',
}).toObject() as ReturnType<NamedError["toObject"]>
expect(SessionRetry.retryable(error)).toBeUndefined()
})
})
describe("session.message-v2.fromError", () => {