Skip to content

fix(opencode): surface silent provider quota limits (D9) - #184

Merged
paulocorcino merged 1 commit into
mainfrom
fix/opencode-quota-limit
Jul 11, 2026
Merged

fix(opencode): surface silent provider quota limits (D9)#184
paulocorcino merged 1 commit into
mainfrom
fix/opencode-quota-limit

Conversation

@paulocorcino

Copy link
Copy Markdown
Owner

Problem

An opencode run against a provider whose quota is exhausted could stall for the
full per-issue wall timeout (60m in the observed case) and then be misclassified
as Timeout with saw_error=false
— so stop-on-limit never fired and the queue
kept burning budget on subsequent issues.

Root cause: some providers never emit a {type:"error"} event on a quota block.
Z.ai's zai-coding-plan (GLM) treats the AI_APICallError: Usage limit reached
as a retryable stream error and loops on exponential backoff, printing it only
to opencode's own log
. The --format json stream stayed silent, so
parse_opencode_limit (a JSON-event scan) saw nothing. Observed live 2026-07-11
(FinCal #71, glm-5.2 — 5-hour cap); an equivalent kimi billing cycle block was
seen earlier.

Fix

  • command: pass --print-logs --log-level ERROR so opencode's own logfmt logs
    land on the stderr the headless runner already drains.
  • events: add parse_opencode_log_limit, a substring scan over the combined
    stdout+stderr log for the usage limit reached / billing-cycle wording that only
    appears in the logfmt lines (the JSON-event scan structurally can't see it), with
    a text-based reset-hint extractor that handles Z.ai's reset at <ts>.
  • lib (execute): fall back to the log scan when the JSON scan finds no limit, so
    the run classifies as Outcome::Limit (stop-on-limit) instead of Timeout.

Fixtures use the exact captured log lines (glm-5.2 5h cap, kimi billing cycle).

Scope / follow-up

This corrects the classification but still waits for the child to exit (the wall
timeout). Killing the child on first detection to reclaim the wasted time is a
deliberate follow-up in the shared headless runner — held back as a separate change
since it touches plumbing used by all adapters. As a zero-code mitigation, a lower
--max-minutes-per-issue bounds the wasted time today.

Verification

cargo fmt --check, cargo clippy -p ralphy-agent-opencode --all-targets -- -D warnings,
and cargo test -p ralphy-agent-opencode (53 passed) all green on the main base.

🤖 Generated with Claude Code

Some providers never emit a `{type:"error"}` event on a quota block: Z.ai's
`zai-coding-plan` (GLM) treats the `AI_APICallError: Usage limit reached` as a
retryable stream error and loops on backoff, printing it only to opencode's own
log. The `--format json` stream stayed silent, so `parse_opencode_limit` (a
JSON-event scan) saw nothing and the run stalled until the 60m wall timeout,
misclassified as `Timeout` with `saw_error=false` (observed live, FinCal #71).

- command: pass `--print-logs --log-level ERROR` so opencode's logfmt logs land
  on the stderr the headless runner already drains.
- events: add `parse_opencode_log_limit`, a substring scan over the combined
  stdout+stderr log for the "usage limit reached" / billing-cycle wording that
  only appears in the logfmt lines, with a text-based reset-hint extractor
  (handles Z.ai's `reset at <ts>`).
- lib (execute): fall back to the log scan when the JSON scan finds no limit, so
  the run classifies as `Outcome::Limit` (stop-on-limit) instead of `Timeout`.

Fixtures use the exact captured lines (glm-5.2 5h cap, kimi billing cycle).

Note: this corrects the classification but still waits for the child to exit
(the wall timeout). Killing the child on first detection to reclaim the wasted
time is a follow-up in the shared headless runner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@paulocorcino
paulocorcino merged commit 460dfdd into main Jul 11, 2026
1 of 3 checks passed
@paulocorcino
paulocorcino deleted the fix/opencode-quota-limit branch July 11, 2026 21:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant