Repository navigation
Description
[Bug]: [qoder] Routed turn echoes OpenCodex's own [Tool call: ...] replay envelope into assistant text (85 KB), because the qoder adapter never mounts ToolEnvelopeEchoFilter
Client or integration
Codex App
Area
Provider adapter
Summary
A routed qoder-cn turn streamed ~85 KB of OpenCodex's own history-replay envelope into the assistant text instead of model output. The leaked block opens with:
[Tool call: mcp__opencodex_exec (call_id: call_00_...) with args: {"input":"const cwd = ...\n..."}]
and continues for tens of kilobytes, with \n and \" visible literally (the args JSON was stringified, so the client shows escape sequences rather than line breaks). The turn still reported status: 200 / terminalStatus: completed, so nothing in the request log flags it — the user just sees a wall of escaped JSON.
Expected: the envelope is an OpenCodex-internal prompt construct. It must never reach the client as assistant text. #4190 established that rule for qoder vendor markup; this is the same class of leak through a different marker.
This is not a duplicate of #4190. #4190 fixed vendor scaffolding (<functions.exec>, <parameter name="cmd">) via QoderScaffoldFilter. The text we received is the envelope OpenCodex itself generates at src/adapters/coding-agent/protocol.ts:753:
parts.push(`[Tool call: ${name} (call_id: ${part.id}) with args: ${args}]`);Reproduction
Not reliably forceable — it is a model-behaviour echo, so treat this as an observed case with the conditions that produced it.
- Configure
qoder-cn(adapterqoder, baseUrlhttps://qoder.cn, PAT auth), modelDeepSeek-Flash. - Run a long agent-style Codex App turn that performs many tool calls (the turn in question called an MCP tool ~15 times over several minutes).
- At some point the model starts re-emitting the replayed envelope lines as its own text; the remaining output is the envelope plus escaped JSON.
Observed on a turn with:
durationMs: 97425deliverySummary: {"adapterEvents":3,"relayedEvents":15,"semanticBytes":85534,"sideEffectEvents":0,"terminalEvents":1}status: 200,terminalStatus: completed
Version
2.89.0
Operating system
Windows 11 (build 26200), win32 x64
Provider and model
qoder-cn / DeepSeek-Flash (also reproduced with Qwen3.8-Flash on neighbouring turns)
Logs or error output
requestId: ocx-7ae043b7ec86bb616494ee4719e2ea21
time: 2026-10-22T06:10:20Z
provider: qoder-cn model: DeepSeek-Flash
status: 200 terminalStatus: completed durationMs: 97425
delivery: adapterEvents=3 relayedEvents=15 semanticBytes=85534 terminalEvents=1
Root cause (as read from 2.89.0)
There is already a filter for exactly this shape:
src/lib/tool-envelope-echo-filter.ts
const MARKERS = ["[Tool Result]", "[Tool Error]", "[tool_result]", "[Tool Call]", "[Tool call:"] as const;
It is mounted on two adapters only:
src/adapters/cursor.ts → new ToolEnvelopeEchoFilter() (mid-stream)
src/server/grok-upstream-envelope-echo.ts → ToolEnvelopeEchoFilter (streaming + non-streaming)
qoder → not mounted
The qoder text channel passes through guardQoderScaffolding() → QoderScaffoldFilter, which is the #4190 vendor-markup guard and does not know about [Tool call: / [Tool Result] envelope prefixes. So for qoder, the envelope passes straight through.
Suggested fix
- Mount
ToolEnvelopeEchoFilteron the qoder text channel the same waycursor.tsdoes, so the echo is stripped (or turned into a terminal 502) rather than streamed. - As defence in depth, add the five
MARKERSprefixes toQoderScaffoldFilterso qoder has one guard that covers both vendor markup and envelope echo.
Config (redacted)
{
"providers": {
"qoder-cn": {
"adapter": "qoder",
"baseUrl": "https://qoder.cn",
"authMode": "key",
"defaultModel": "Qwen3.8-Max",
"liveModels": true
}
}
}Checks
- I searched existing issues and documentation (
#4190,#4596,#6689reviewed; none cover the qoder envelope echo). - I removed secrets, tokens, account details, request credentials, and personal data.
Activity
[-][Bug]: [qoder] Routed turn echoes OpenCodex own "[Tool call: ...]" replay envelope (85 KB) — qoder adapter never mounts ToolEnvelopeEchoFilter[/-][+][Bug]: [qoder] Routed turn echoes OpenCodex's own "[Tool call: ...]" replay envelope (85 KB) — qoder adapter never mounts ToolEnvelopeEchoFilter[/+]github-actions commentedon Oct 22, 2026
Automated translation bookkeeping — detected language: English.
Ingwannu commentedon Oct 22, 2026
Confirmed the current gap and opened Draft #7521 at
6ed26cd8fd. The new regression group failed 24 of its initial 25 cases before implementation, reproducing the escaped replay output independently of a live model.The fix reuses the existing line/fence-aware
ToolEnvelopeEchoFilterafter Qoder's vendor text guard. Echoed call/result envelopes and unverified overflowing fenced holds produce a single non-retryablevendor_scaffold_detected502; a later success is suppressed. Ordinary marker prose, closed fenced examples, native tool events and the existing thinking guard retain their contracts. It does not duplicate marker grammar in the vendor scanner or change image/CLI-resolution behavior.Final validation: 243 passed / 0 failed on Bun 1.4.3 (guard, actual injected adapter path, shared recognizer, readmission, structure and size gates); 171 passed / 0 failed on shipped Bun 1.4.2. Privacy and whitespace checks passed. Typecheck, complete docs build and broader suites remain deferred to hosted validation under the documented local limits, so this is a reviewable Draft fix, not a shipped fix. No live Qoder/PAT/runtime was changed.
fix(qoder): provider-only catalog, replay-envelope refusal and isolat…
lidge-jun commentedon Oct 24, 2026
Fixed on dev by #7545 (5253164), which carries #7521 by @Ingwannu. Qoder assistant text now passes through the shared tool-envelope echo filter: a replayed
[Tool call: ...]envelope ends the turn with non-retryablevendor_scaffold_detectedinstead of being published as assistant text. This ships in the next release after 2.92.0; please reopen if it recurs.