Repository navigation
fix(cli): end an opencode turn on its terminal provider error - #989
Conversation
opencode logs a provider error ("stream error … Free usage limit
reached") and keeps retrying, but never reports it over ACP: the prompt
stays open with no update until the 90 s idle watchdog trips and the user
gets a generic retry bubble. On 2026-10-22 OpenCode Zen's free quota ran
out for every Box (they share fleet-1's egress IP) and half the opencode
turns of the day failed that way, for all four opencode users.
- spawn `opencode acp --print-logs --log-level ERROR` so its errors
reach the adapter stderr we already read;
- AdapterSpec.fatalTurnStderr: a matching line ends the in-flight prompt
at once and cancels the turn (opencode would keep retrying);
- a free-limit failure gets its own bubble (add a key, switch agent, or
OpenCode Go) and the cli_turn code AGENT_FREE_LIMIT.
Only terminal errors match (free limit, quota, invalid key, unauthorized);
rate limits and 5xx stay with opencode's own retry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c5d8d3a1bf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } as unknown as AcpClientOptions); | ||
| const internals = client as unknown as ClientInternals; |
There was a problem hiding this comment.
Construct a valid typed client fixture without double assertions
The new fixture bypasses required constructor fields using as unknown as AcpClientOptions: its adapter omits requiresAgentBinary and waitForBinary, and the options omit onRequestPermission. It then uses another double assertion to replace private internals. This violates the repository's explicit non-negotiable typing rule and prevents TypeScript from validating the fixture against the actual client contract. Supply complete typed options and mock the connection boundary instead of silencing these mismatches.
AGENTS.md reference: AGENTS.md:L12-L12
Useful? React with 👍 / 👎.
| this.startupFailureReject = null; | ||
| } | ||
| } | ||
| if (trimmed) this.onAdapterStderrLine(trimmed); |
There was a problem hiding this comment.
Buffer complete stderr lines before checking fatal errors
When a provider-error log line is split across stderr data events, this forwards each fragment independently and discards the boundary. The new fatal regex requires both message="stream error" and error.error="…" in the same string, so splitting between those fields means neither fragment matches: the failed turn keeps retrying until the idle watchdog fires instead of being cancelled immediately. Stream chunks are not guaranteed to align with lines; retain the unfinished suffix between events and pass only complete lines to the matcher. The new tests call onAdapterStderrLine directly, so they do not exercise this transport case.
Useful? React with 👍 / 👎.
Causa raíz
El 2026-10-22, 40 de los 79 turnos de opencode fallaron, y les pasó a los 4 usuarios que lo usaron. Saltó el guardrail
[guardrail] product breakage.El log de opencode de esos Boxes muestra que el cupo gratuito de OpenCode Zen estaba agotado:
session/promptabierto sin enviar ningún update.ACP prompt idle, y el usuario ve el genérico "the agent stopped responding".Lo reproduje en la imagen del Box apuntando opencode a un servidor que responde 429 de cuota agotada. Sin el cambio, el prompt queda colgado y opencode reintenta 14 veces en 25 s.
Arreglo
opencode acp --print-logs --log-level ERROR: los errores de opencode llegan al stderr que ya leemos. En un turno normal no imprime nada: verificado,end_turnen 0,9 s.AdapterSpec.fatalTurnStderr+AcpClient.onAdapterStderrLine: con un turno en curso, una línea con un error terminal lo termina al instante y lo cancela, para que opencode deje de reintentar. Solo cuentan errores que un reintento no arregla (cupo gratuito, quota, clave inválida, unauthorized); los 429 de rate limit y los 5xx siguen en manos del retry de opencode.AGENT_FREE_LIMIT_MESSAGE: explica el límite y da tres salidas (clave propia en Profile › Agents, otro agente, OpenCode Go).cli_turnreporta el códigoAGENT_FREE_LIMIT.Tests
__tests__/agents/acp.fatalTurnStderr.test.ts(8 tests) cubre:__tests__/agents/acp*: 875 en verde.🤖 Generated with Claude Code