Skip to content

fix: keep OpenCode turns alive after rejected tools - #15

Merged
developingchet merged 2 commits into
mainfrom
fix/opencode-tool-rejection
Sep 28, 2026
Merged

developingchet merged 2 commits into
mainfrom
fix/opencode-tool-rejection

Conversation

@developingchet

Copy link
Copy Markdown
Owner

Two failures when the model reaches for OpenCode's own tools, both on OpenCode 1.18.32.

500 on structured output. A turn with several assistant messages was re-read through GET /session/:id/message. That listing also returns the user message, and OpenCode fails to encode a stored output format on it (HTTP 400, anomalyco/opencode#26929 and #40169), so any json_schema request where the model tried a tool became a 500. The earlier assistant messages are now fetched one by one with GET /session/:id/message/:messageID. If that still fails, the gateway answers from the final message instead of failing the request. The listing is kept only for the case where the event stream is down and the message ids are unknown, and the same fallback applies there.

Empty replies. A plain permission rejection ends an OpenCode turn, and rejecting one request also rejects the rest of the session's pending requests. A model that called several tools at once could therefore stop before writing anything, and the follow-up replies for those siblings got 404. The managed backend now sets experimental.continue_loop_on_deny, so each rejection is handed back to the model. A 404 from a permission or question reply is logged at debug level as already answered.

Attached servers keep their own config, so they need continue_loop_on_deny set themselves to get the second fix.

Testing

  • npm run lint, npm test (332 pass). A new integration test drives a multi-message json_schema turn through a fake backend whose listing returns 400 when a stored message has a format, as OpenCode does. It fails on main and passes here.
  • npm run test:live against a managed OpenCode 1.18.32 backend: all checks pass.
  • Six tool-baiting prompts (read a file, list files, run ls, ask a question, "fix the bug", web search), each as text and as json_schema, on big-pickle:
    • main: four 500s, one empty reply.
    • This branch: 12 of 12 non-empty 200s, with valid JSON for every structured request.

A turn that spans several assistant messages was re-read through the
session listing, which also returns the user message. OpenCode 1.18 fails
to encode a stored output format on that listing (HTTP 400), so every
structured-output request whose model tried a tool ended as a 500. Fetch
the earlier assistant messages one by one instead, and answer from the
final message if the re-read still fails.

A plain rejection ends an OpenCode turn, and rejecting one permission
request rejects the rest of the session's pending requests with it, so a
model that called several tools at once could stop with an empty reply.
Enable experimental.continue_loop_on_deny so each rejection goes back to
the model, and treat a 404 from a permission or question reply as already
answered rather than a failure.
@developingchet
developingchet merged commit 89e70b8 into main Sep 28, 2026
9 checks passed
@developingchet
developingchet deleted the fix/opencode-tool-rejection branch September 28, 2026 01:19
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