Skip to content

[Bug]: [qoder] Routed turn echoes OpenCodex's own "[Tool call: ...]" replay envelope (85 KB) — qoder adapter never mounts ToolEnvelopeEchoFilter #7517

Closed

Description

@duanzhijian200443

[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.

  1. Configure qoder-cn (adapter qoder, baseUrl https://qoder.cn, PAT auth), model DeepSeek-Flash.
  2. 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).
  3. 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: 97425
  • deliverySummary: {"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

  1. Mount ToolEnvelopeEchoFilter on the qoder text channel the same way cursor.ts does, so the echo is stripped (or turned into a terminal 502) rather than streamed.
  2. As defence in depth, add the five MARKERS prefixes to QoderScaffoldFilter so 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, #6689 reviewed; none cover the qoder envelope echo).
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. changed the title [-][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[/+] on Oct 22, 2026
  2. github-actions commented on Oct 22, 2026

    @github-actions
    Contributor

    Automated translation bookkeeping — detected language: English.

  3. Ingwannu commented on Oct 22, 2026

    @Ingwannu
    Owner

    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 ToolEnvelopeEchoFilter after Qoder's vendor text guard. Echoed call/result envelopes and unverified overflowing fenced holds produce a single non-retryable vendor_scaffold_detected 502; 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.

  4. added
    bugSomething isn't working
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    toolstool_calls, MCP, web-search / sidecar tools
    on Oct 22, 2026
  5. lidge-jun commented on Oct 24, 2026

    @lidge-jun
    Owner

    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-retryable vendor_scaffold_detected instead of being published as assistant text. This ships in the next release after 2.92.0; please reopen if it recurs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingproviderProvider adapters, OpenAI-compat presets, upstream API quirkstoolstool_calls, MCP, web-search / sidecar tools

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions