You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: OpenCode Go native Responses path leaks undeclared apply_patch and shows aborted #1700
OpenCodex 2.17.0 leaks an undeclared top-level apply_patch function call through the native OpenAI Responses passthrough path when using OpenCode Go. Codex App displays aborted, while the target file remains unchanged.
Expected behavior: an undeclared routed tool should fail closed with an explicit compatibility error before reaching the Codex executor. The supported editing path should remain exec -> tools.apply_patch(...).
Reproduction
Start OpenCodex on port 10100.
Use exec -> tools.apply_patch(...) to create a temporary file containing v1.
Invoke top-level apply_patch with a patch changing v1 to v2.
Codex App displays aborted.
The file remains v1; content and mtime are unchanged.
Run the equivalent patch through exec -> tools.apply_patch(...):
an incorrect expected line returns a real apply_patch verification failed error;
the correct v1 -> v3 patch succeeds.
The request-visible catalog contains exec, wait, and request_user_input, but no top-level apply_patch schema. The session nevertheless receives function_call(name=apply_patch).
Version
OpenCodex 2.17.0
Operating system
macOS 27.0, Apple Silicon
Provider and model
Provider: opencode-go
Model: deepseek-v4-flash
At reproduction time, the provider default was openai-chat, while this model had an explicit openai-responses adapter override.
Logs or error output
Codex result: aborted
No corresponding custom_tool_call_output was emitted.
OpenCode Go usage record was HTTP 200.
No upstream aborted, OpenCodex response.failed, approval denial, or sandbox denial was observed.
Screenshots and supporting files
No screenshots. Reproduction artifacts were written only under /tmp.
The native Responses passthrough now fails closed when a routed provider returns a tool call the caller never declared. All four close conditions are covered:
No-catalog passthrough preserved. The guard only arms when the request carries a readable, non-empty tool catalog and the provider is not authMode: "forward". A request with no tools field, or tools: [], is relayed exactly as before — tested on both transports, because Copilot/DeepSeek passthrough depends on it.
Hosted-tool rewrite keeps the caller's catalog. Outbound wire names are unioned with the original caller names before either transport branches, so a request declaring image_gen.generate that ships {type:"image_generation"} upstream still gets its own name back without tripping the guard. A name declared in neither place — #1700's apply_patch — is still refused with a 502.
A rejected turn does not enter previous_response_id state. Non-streaming validates before the cache write. Streaming needed more: an upstream can announce the undeclared call in response.output_item.added, trip the client guard, and then close with a response.completed whose output is empty — the terminal snapshot then looks clean and the refused turn would have been remembered anyway. Rejection is now sticky for the whole turn, set from every parsed payload on the inspection side. That required adding an onParsedPayload seam to SseInspectorHandlers and wiring it through InspectionConsumerOptions so both the eager and tee consumers observe it; without that the flag could never have been set.
Streaming and non-streaming both covered. Guard rejection, no-catalog passthrough, and continuation-state exclusion each have regressions on both paths. The new streaming continuation test was driven red against the unfixed flag before landing.
Verified at the exact merge head: full Cross-platform CI green, all four test shards plus the Windows and macOS jobs.
Closing as completed. The fix is on dev and ships with the next release; it is not in a published version yet.
Activity
fix(responses): fail closed when a routed provider calls an undeclare…
lidge-jun commentedon Aug 16, 2026
Fixed on
devby #1788.The native Responses passthrough now fails closed when a routed provider returns a tool call the caller never declared. All four close conditions are covered:
No-catalog passthrough preserved. The guard only arms when the request carries a readable, non-empty tool catalog and the provider is not
authMode: "forward". A request with notoolsfield, ortools: [], is relayed exactly as before — tested on both transports, because Copilot/DeepSeek passthrough depends on it.Hosted-tool rewrite keeps the caller's catalog. Outbound wire names are unioned with the original caller names before either transport branches, so a request declaring
image_gen.generatethat ships{type:"image_generation"}upstream still gets its own name back without tripping the guard. A name declared in neither place — #1700'sapply_patch— is still refused with a 502.A rejected turn does not enter
previous_response_idstate. Non-streaming validates before the cache write. Streaming needed more: an upstream can announce the undeclared call inresponse.output_item.added, trip the client guard, and then close with aresponse.completedwhoseoutputis empty — the terminal snapshot then looks clean and the refused turn would have been remembered anyway. Rejection is now sticky for the whole turn, set from every parsed payload on the inspection side. That required adding anonParsedPayloadseam toSseInspectorHandlersand wiring it throughInspectionConsumerOptionsso both the eager and tee consumers observe it; without that the flag could never have been set.Streaming and non-streaming both covered. Guard rejection, no-catalog passthrough, and continuation-state exclusion each have regressions on both paths. The new streaming continuation test was driven red against the unfixed flag before landing.
Verified at the exact merge head: full Cross-platform CI green, all four test shards plus the Windows and macOS jobs.
Closing as completed. The fix is on
devand ships with the next release; it is not in a published version yet.fix(xai): require the hosted call-id prefix, not just the item type
fix(xai): stop the undeclared-tool guard from killing hosted x_search…
fix(xai): require the hosted call-id prefix, not just the item type
fix(xai): stop the undeclared-tool guard from killing hosted x_search…
fix(xai): require the hosted call-id prefix, not just the item type
fix(xai): stop the undeclared-tool guard from killing hosted x_search…
7 remaining items
feat(guard): per-provider undeclaredToolAllowlist drops phantom tool …
fix(xai): stop the undeclared-tool guard from killing hosted x_search…
feat(xai): opt-in x_search alongside hosted web search on the Respons…
test(responses): cover responses fail-closed regression and fix deliv…
fix(catalog,bridge): write every slug once and scope declared-tool me…
fix(responses): fail closed when a routed provider calls an undeclare…
fix(xai): stop the undeclared-tool guard from killing hosted x_search…
feat(xai): opt-in x_search alongside hosted web search on the Respons…
fix(catalog,bridge): write every slug once and scope declared-tool me…
fix(responses): emit the tool name the undeclared-tool guard authoriz…
fix(responses): emit the tool name the undeclared-tool guard authoriz…
fix(server): admit direct mcp__ tool calls through code-mode exec
fix(server): admit direct mcp__ tool calls through code-mode exec
fix(server): admit direct mcp__ tool calls through code-mode exec
fix(guard): apply emitted call-shape repair in the native passthrough…