Skip to content

opencode run hangs indefinitely when the usage quota is exhausted, instead of reporting the error it already has #40747

Closed as not planned

Description

@nab-x

Summary

When a model's usage quota is exhausted, opencode run never exits and never
reports the reason
. The process hangs indefinitely with no output on stdout and
nothing but a banner on stderr — even though the error is known internally within
~170 ms and written to opencode's own log file.

Any non-interactive use (scripts, CI, an app shelling out to the CLI) therefore
hangs until an external timeout kills it, and the caller has no way to tell an
exhausted quota from a slow model.

The interactive TUI handles this case properly, so this looks specific to the
non-interactive run path.

Version

opencode 1.17.11, Linux x86_64.

Reproduction

With a subscription whose quota is exhausted:

$ timeout 90 opencode run --model opencode-go/<any-model> "say hi"
rc=124            # killed by `timeout`, not by opencode
stdout: 0 bytes
stderr: 34 bytes  # only: ESC[0m "> build · <model>" ESC[0m

Same result with --format json (stdout and stderr both 0 bytes), and at
120 s and 300 s ceilings. The process never terminates on its own.

It knows almost immediately

--print-logs --log-level DEBUG on the same run:

11:42:33.920 INFO  run=… message=stream providerID=opencode-go modelID=<model> …
11:42:34.091 ERROR run=… message="stream error" … error.error="AI_APICallError: Monthly usage limit reached. Resets in N days. …"
11:42:36.448 ERROR run=… message="stream error" … small=true agent=title … error.error="AI_RetryError: Failed after 3 attempts. Last error: Monthly usage limit reached. …"

171 ms from stream start to a fully-formed error. It reaches the log file and
then goes nowhere else, and the process continues to hang.

(The second line is the title-generation agent hitting the same wall after its
own retries — possibly relevant to why the run never unwinds.)

Expected

opencode run should exit non-zero and print the provider's error. With
--format json, emitting the existing error envelope on stdout would be enough:

{"type":"error","error":{"type":"GoUsageLimitError","message":"Monthly usage limit reached. …"}}

That is the shape errors already take on that stream, so a caller could parse it
with no new contract.

Actual

Exit code: none — the process must be killed externally.
stdout: empty.
stderr: a decorative banner only.

Why it matters

A CLI that hangs forever is worse than one that fails: a wrapper cannot
distinguish "out of quota" from "model is slow" and has to wait out its full
timeout budget before giving up, on every subsequent call too. Exiting non-zero
with the message that is already in hand would make this a two-line failure
instead.


Identifiers (workspace, session, run ids) redacted from the log excerpts above.

Activity

  1. DaisukeYoda commented on Aug 13, 2026

    @DaisukeYoda

    Same thing on macOS 15.3.1 arm64 with opencode 1.18.15, so it isn't Linux- or 1.17.11-specific.

    Mine ran 78 hours before I killed it by hand — there's no eventual unwind, the 90/300 s ceilings above were just far too short. It wasn't blocked on a dead socket either: 55 min of CPU over 78.9 h, and the only thing it logged that whole time was its hourly snapshot cleanup.

    Three of them had piled up. They were wrapped in caffeinate -i, so the machine couldn't sleep for 3 days, and launchd won't relaunch a job whose previous instance is still alive — so the scheduled runs silently stopped happening with no failure signal anywhere. That last part is the one that hurt: it's not just "job hangs", it's "job hangs and quietly disables every later run".

    (I filed #42268 before spotting this issue; closed as a duplicate.)

  2. DaisukeYoda commented on Aug 13, 2026

    @DaisukeYoda

    Traced it: the process is sleeping until the quota resets.

    retryable() classifies GoUsageLimitError as retryable, and delay() honors the provider's retry-after — days, for a monthly limit. While the schedule sleeps, halt() never runs, so no session.error and no idle are published, and loop() in cli/cmd/run.ts breaks only on idle with no branch for retry status.

    My logs match: only level=INFO message=process, never the ERROR one halt() writes. And with no retry-after header the 5 retries would have finished in under 3 minutes and exited 1, which isn't what happened.

    Worth noting for whoever picks this up: the long sleep looks intentional (0a2d7af removed an earlier 10-minute cap, with tests). PR #42289 leaves the timing alone and only changes the non-interactive loop.

  3. added a commit that references this issue on Aug 13, 2026
    fc93a0f
  4. zeroliu commented on Sep 8, 2026

    @zeroliu

    We’re seeing a similar symptom in Obsidian Copilot, which uses OpenCode through ACP rather than opencode run.

    With OpenCode 1.18.16, a user’s logs show Gemini high-demand errors and a free-tier quota error, followed by retries. The UI eventually stops generating without an answer or error banner.

    One difference: our ACP trace shows the affected turns returning stopReason: "end_turn" with outputTokens: 0, rather than remaining pending indefinitely. So this may be a related error-propagation gap rather than the same retry-sleep bug.

    Does the proposed fix also cover ACP? It would help ACP clients to receive session-scoped retry status, including the provider’s reason and next retry time, and a protocol-visible error when the turn cannot continue. Currently, users have to inspect opencode.log to discover that the provider encountered an error.

    Our report and logs: logancyang/obsidian-copilot#3104

  5. thdxr commented on Oct 11, 2026

    @thdxr
    Member

    Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.

    — from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions