Skip to content

fix(cli): end an opencode turn on its terminal provider error - #989

Merged
edgar-durand merged 1 commit into
mainfrom
fix/opencode-free-limit
Oct 22, 2026
Merged

edgar-durand merged 1 commit into
mainfrom
fix/opencode-free-limit

Conversation

@edgar-durand

Copy link
Copy Markdown
Collaborator

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:

level=ERROR message="stream error" providerID=opencode modelID=mimo-v2.6-flash-free
error.error="AI_APICallError: Free usage limit reached. Subscribe to OpenCode Go to keep going"
  • Cuatro Boxes de cuatro usuarios distintos chocaron con ese límite a partir de las 16:07 UTC, porque todos comparten la IP de salida de fleet-1.
  • opencode no reporta el error por ACP: reintenta contra el proveedor y deja el session/prompt abierto sin enviar ningún update.
  • Nuestro watchdog corta el turno a los 90 s con 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_turn en 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.
  • Burbuja propia, AGENT_FREE_LIMIT_MESSAGE: explica el límite y da tres salidas (clave propia en Profile › Agents, otro agente, OpenCode Go). cli_turn reporta el código AGENT_FREE_LIMIT.

Tests

  • __tests__/agents/acp.fatalTurnStderr.test.ts (8 tests) cubre:
    • el patrón con la línea real del Box;
    • que no corta ante un 429 de rate limit ni ante un 5xx;
    • que corta el turno y lo cancela;
    • que una línea fatal fuera de un turno solo se registra;
    • la burbuja, con y sin texto parcial;
    • los argumentos del adaptador.
  • Las suites __tests__/agents/acp*: 875 en verde.

🤖 Generated with Claude Code

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>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T21:44:26.844357Z c5d8d3a PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +48 to +49
} as unknown as AcpClientOptions);
const internals = client as unknown as ClientInternals;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@edgar-durand
edgar-durand merged commit 71d501f into main Oct 22, 2026
14 checks passed
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.

2 participants