Repository navigation
Description
Client or integration
Codex App (also observed from a Codex task using the local proxy)
Area
Proxy and routing / Responses WebSocket continuation
Summary
A tool-heavy Codex task routed through OpenCodex can keep issuing new continuation requests after each tool result, repeatedly emitting near-identical progress messages and re-selecting the same browser action instead of reaching a terminal response.
I expected the Responses bridge to preserve one coherent tool-turn continuation and to surface a terminal completion (or a bounded, actionable error) once the task is no longer making progress.
This does not look like a raw HTTP retry of one request: the observed request IDs were distinct and most responses were HTTP 200. Stopping/bypassing OpenCodex immediately stopped the behavior in the same Codex environment.
Reproduction
This is currently intermittent rather than a short deterministic reproduction.
- Use Codex App with its OpenAI endpoint pointed at a local OpenCodex proxy.
- Enable WebSocket support and route the native OpenAI/Codex Responses traffic through the OpenAI Responses forward route.
- Start a long, tool-heavy task (browser/computer-use actions; the original task also used
/goalauto-continuation). - Put the task into a non-progressing end state, such as a browser workflow that cannot finish the final external action.
- Observe that after tool outputs the client keeps producing new continuation turns, near-identical status text, and repeated selection of the same tool target.
A second short diagnostic task showed the same pattern without /goal or subagents: 13 similar commentary updates over about three minutes, backed by 13 distinct Responses requests, all HTTP 200. This makes /goal an amplifier rather than the only cause.
Version
- Incident observed with
@bitkyc08/opencodex 2.7.33 - Current local installation is now
2.7.34; I have not deliberately rerun the long scenario yet because it can loop for a long time. - Codex CLI:
0.144.1 - Codex App build:
26.715.21425
Operating system
macOS 26.5.2 (build 25F84), Apple Silicon
Provider and model
Native OpenAI/Codex login forward route with the openai-responses adapter. I am deliberately omitting account/model identifiers because the behavior appears to be at the bridge/continuation level.
Logs or error output
Sanitized observations from one incident:
duration: about 77 minutes
assistant messages: 293
tool calls: 463
unique proxy request IDs: 75
HTTP results: 73 x 200, then 2 x 502 during interruption
separate diagnostic:
near-identical commentary updates: 13
distinct Responses requests: 13
HTTP results: 13 x 200
I inspected the local Responses state after the diagnostic. It contained distinct resp_* states with usable cursors and accumulated tool output; I did not find reuse of one request ID or verbatim replay of the same assistant commentary as developer input.
Supporting context
This may be in the same compatibility family as openai/codex#25327 (custom proxy + WebSocket + tool calls, requests returning 200 while continuation handling fails), but the symptom here is repeated continuation/tool selection rather than a disconnected client.
I also reviewed OpenCodex #215 and #272:
- fix(responses): strip mismatched agent_message item ids before passthrough #215 addresses mismatched
agent_messageIDs causing repeated HTTP 400, which does not match the 200-heavy incident. - fix: add opt-in responses item id repair #272 repairs unstable passthrough item IDs/activity-card correlation. It is relevant to UI duplication, but it does not appear to change
src/server/ws-bridge.tsor directly bound actual continuation loops.
Could maintainers advise whether the bridge guarantees exactly one terminal event for each tool-output continuation, and which redacted bridge events/state fields would be most useful to capture next? I can provide a minimal sanitized trace privately in the issue if needed.
Redacted configuration
{
"local_proxy": "http://127.0.0.1:10100/v1",
"websockets": true,
"provider": "openai",
"adapter": "openai-responses",
"upstream": "native OpenAI/Codex forward route"
}Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, task content, and personal data.
Activity
lidge-jun commentedon Jul 23, 2026
Thanks for the thorough write-up. The symptom you describe — distinct request IDs, HTTP 200 responses, repeated tool selection without terminal completion — is clearly different from the known issues:
agent_messageIDs causing HTTP 400 retries (fixed).Your second diagnostic (13 requests, 13 identical 200s, no
/goalor subagents) is particularly useful because it narrows the surface: the loop is not driven by/goalauto-continuation alone.What would help narrow this down:
To determine whether the bridge is emitting a terminal event that the client ignores, or whether the bridge itself is re-initiating continuations, we'd need a sanitized trace of the WS event sequence. Specifically:
response.completedevents appear between continuation turns (does each continuation's response terminate cleanly?).resp_*state get created, or is the same response ID reused?response.output_item.doneevents fire for each tool call output before the next continuation starts.If you can reproduce on v2.7.34, enabling
OCX_DEBUG=bridgewould capture the relevant event flow (request bodies are not logged; only event types and IDs).Alternatively, if you have the local Responses state from the diagnostic session, the cursor chain and
resp_*creation timestamps would show whether the bridge or the client is driving the loop.kdnsna commentedon Jul 23, 2026
I did a deeper state/source correlation before adding more speculation. There is now a concrete deterministic defect in the native passthrough continuation path that matches the same-thread loop.
Observed chain from the looping turn
The affected Codex App turn was a normal request to track this issue. Between 09:46:01 and 09:47:44 it emitted six near-equivalent progress/commentary messages. Each was followed by a different successful tool call, so this was not an identical HTTP/SSE replay.
The six persisted passthrough states were distinct and grew as follows:
Every continuation added exactly:
custom_tool_call_output, andThose developer messages are byte-identical: 538 characters, same SHA-256 prefix
d9d2e296ad4382a9. Without exposing the prompt body, structural checks identify it as OpenCodex's generated multi-agent guidance: it has the<multi_agent_mode>wrapper and mentionsspawn_agent,fork_turns, the preferred sub-agent, and the roster.The local config has an
injectionModelplussubagentModels;multiAgentGuidanceEnabledis unset, which means enabled by default.Source-level cause in v2.7.34
The ordering appears non-idempotent:
expandPreviousResponseInput()restores allprevious.itemsinto the new request input.injectDeveloperMessage().parsed._rawBody;rememberResponseState()stores that already-injected input.previous_response_idexpansion replays the old guidance and the shim appends another copy.That exactly explains the observed +1 identical developer message per tool continuation. It also explains why stopping/bypassing the proxy immediately cleared the behavior in the same thread.
I checked the current
devbranch as well; it is ahead of v2.7.34, but the same expand-then-unconditional-inject sequence remains.Suggested fix / regression test
Either of these should prevent accumulation:
injectDeveloperMessageidempotent for the exact generated guidance already present in the replayed prefix; orrememberResponseState, then inject one fresh copy per outbound request.A regression test could create a Responses request with collaboration tools and generated guidance, record a completed response, then submit two chained
previous_response_idtool-output continuations. Each outbound request and each saved state should contain at most one copy of that exact proxy-generated guidance.As a diagnostic A/B, setting
multiAgentGuidanceEnabled: falseshould remove this accumulator while leaving the underlying Codex collaboration surface intact. I have not changed the user's persistent config yet; I can run that bounded A/B if you want confirmation before a patch.This finding does not yet rule out a separate terminal-event issue, but it is independently sufficient to explain why a tool-heavy native OpenAI turn becomes increasingly biased toward re-planning/repeating the same workflow.
docs: add roadmap for untouched issue sweep (WP0)
Merge pull request #350 from lidge-jun/codex/260724-passthrough-state
lidge-jun commentedon Jul 23, 2026
Fixed on
devby PR #350 (merged 05ee9ec).Root cause was exactly as @kdnsna diagnosed:
expandPreviousResponseInput()replays persisted input (including the previously injected guidance) and the guidance shim then unconditionally appended another copy, whichrememberResponseStatepersisted — +1 identical developer message per tool continuation.The fix takes the idempotent-injection path: the replay-prefix length is now carried from the expansion onto the parsed request, and
injectDeveloperMessagescans that replayed prefix for the exact proxy-generated guidance item, skipping re-insertion when present. Near-match items (different text/shape) still inject fresh guidance, and the guidance-before-compaction_triggerordering invariant is preserved. The suggested two-continuation regression is implemented: two chainedprevious_response_idcontinuations now assert exactly one guidance copy in both the outbound request and saved state.Ships in the next release; available on
devnow. Thank you for the SHA-level evidence trail — it pinned the accumulator precisely.docs: add roadmap for untouched issue sweep (WP0)
Merge pull request lidge-jun#350 from lidge-jun/codex/260724-passthro…