[Fix] Sessions and tasks keep retrying when the inference provider is out of credits - #3167
Merged
brunobergher merged 2 commits intoSep 23, 2026
Merged
Conversation
Recognize credit and quota exhaustion (HTTP 402, ChatGPT usage_limit_reached, OpenAI insufficient_quota, Anthropic credit balance and spend limits, OpenRouter credit limits) in sessions and tasks, end OpenCode's own retry loop, and tell the user which provider ran out. Plain rate limits and OpenRouter's temporary in-flight budget keep retrying. Saving a provider key whose account has no credits no longer fails; it is saved with a warning. Only rejected credentials block the save.
Contributor
Contributor
Author
|
Fixed the one outstanding finding behind the failed Roomote code review check in 427dafba.
Result: the review thread is resolved. The automatic re-review of 427dafb passed: the Roomote code review check succeeded, the review summary reports no new code issues, and all other checks are green, including Test. No visual proof was captured for this backend-only fix because the proof step timed out. |
Credit-exhaustion retries were only stopped when the caller passed event callbacks. Validation and plain helper calls never subscribed, so an exhausted account sat through OpenCode's backoff or timed out instead of returning the credits result. Watch the event stream for every prompt.
brunobergher
marked this pull request as ready for review
September 23, 2026 14:09
brunobergher
requested review from
brunobergher,
daniel-lxs and
mrubens
as code owners
September 23, 2026 14:09
brunobergher
approved these changes
Sep 23, 2026
brunobergher
deleted the
fix/inference-credits-exhausted-0aua5ufl7moxv
branch
September 23, 2026 14:11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
When an inference provider says the account is out of credits or quota, sessions and tasks used to show "The inference provider returned a temporary error. Retrying automatically…" and keep retrying a request that could not succeed. They now stop right away and say:
{provider}is the provider's display name. Foropenai/models it is "ChatGPT (subscription)" when a connected ChatGPT subscription is serving them; otherwise it is the catalog label ("OpenAI", "OpenRouter", "Anthropic", "Roomote inference", …).Detection. A shared, worker-safe detector in
@roomote/types(isInferenceCreditsExhaustedError) is built from what the providers actually return, not guessed:error.type: "usage_limit_reached"("The usage limit has been reached") orusage_not_included, as the Codex CLI'sapi_bridge.rstreats them as fatalinsufficient_quota(type or code),credit_balance_exhausted,organization_spend_limit_exceeded,project_spend_limit_exceeded,organization_usage_limit_exceeded; OpenCode's rewrite of the streamed code ("Quota exceeded. Check your plan and billing details.")rate_limit_errorwithdetails.error_code: "enforced_spend_limit_reached"(monthly cap); 402billing_errormetadata.limit_sourceopenrouter_credits/openrouter_key_limit,error_type: "payment_required", "This request requires more credits…"Structured signals are checked first. Provider-specific phrases are used only because OpenCode's retry status carries just the message text.
Stays retryable: plain 429 rate limits; unidentifiable errors; Gemini's per-minute
RESOURCE_EXHAUSTED429 (it reuses OpenAI's "You exceeded your current quota…" sentence, so the OpenAI phrase also requires OpenAI's docs link); and OpenRouter's in-flight budget 402 (limit_source: openrouter_in_flight_budget). OpenRouter documents that one as a temporary hold sent withRetry-After, so it never reads as out of credits.Sessions (Fast and other non-task inference):
classifyNonTaskInferenceErrorchecks credits before the "explicitly non-retryable" and 429 rules. Exhausted credits therefore no longer receive the one fresh-session retry granted to generic rejections, and no longer receive rate-limit backoff.insufficient_creditswas already a terminal reason, so platform events settle once instead of being re-delivered.Tasks (worker OpenCode harness): the same errors are terminal for OpenCode's internal retry, Roomote's 6× provider-error recovery, and the 429 rate-limit recovery. The terminal transcript message and
terminal_provider_errorevent use the new text. The provider is taken from theproviderIDOpenCode reports for the failing session. The gateway markers decide whetheropenai/andxai/models run on a ChatGPT or Grok subscription.Saving provider keys (product decision). Running out of credits or quota no longer blocks saving or adding a provider. Only rejected credentials (
invalid_credentials) block, so operators can add the provider and top up the account in parallel.invalid_endpointstill blocks endpoint connections.apps/docs/models.mdxnow describes the save behavior and adds a "You seem to have run out of credits" entry under common issues. A changeset is included.How it was tested
New and updated Vitest coverage:
@roomote/types: 40 fixture cases for the detector. They use real provider response bodies, and the negatives include plain rate limits, Gemini's look-alike quota 429, the OpenRouter in-flight 402, an invalid key, and credit wording that appears only in the echoed request.@roomote/cloud-agentsnon-task usage: a credits retry status ends the prompt withoutonProviderRetryand aborts the OpenCode session, including for a helper call and avalidateNonTaskInferencecall that pass no callbacks (both previously sat through OpenCode's backoff until the deadline); classification ordering is also covered.@roomote/cloud-agentsFast service: an exhausted-credits closeout names ChatGPT (subscription), OpenAI, or OpenRouter, runs exactly one inference attempt, and writes no retry notice; a platform-event variant settles once.@roomote/workerharness: a ChatGPT usage-limit retry status and OpenAI/Anthropic/OpenRouter session errors fail the task without any continue prompt or retry notice.@roomote/web: provider validation only blocks on credentials and returns the credits warning; the Models command saves an out-of-credits key and an endpoint provider that returns 402; the wizard shows the warning and continues.Whole-suite runs of the touched files pass: types, non-task usage, Fast service, worker harness, recovery and rate-limit units, and the web command, validation, and UI suites.
oxlint --deny-warnings,format:check, web and worker ESLint,tscfor the four touched packages, the pre-pushcheck-types:fast/knip, and the docs check (pnpm --filter @roomote/docs check) all pass.Not exercised end to end against real exhausted provider accounts; the provider shapes come from the OpenCode v1.18.30 and Codex CLI sources and the Anthropic and OpenRouter error docs. No browser proof: the user-visible changes are transcript text and a warning toast that needs a real out-of-credits key to reach.
Checklist
[Fix],[Feat],[Improve],[Refactor],[Docs], or[Chore]followed by a user-facing descriptionpnpm lintandpnpm check-typespass locallypnpm changeset