Skip to content

[qoder] Vendor CLI agent scaffolding leaks into routed output (MCP lazy-loading reminder + tool-call markup) #4190

Closed

Description

@gaterking

Client or integration

Codex App

Area

Provider adapter

Summary

Routing turns through the Qoder (Global) adapter intermittently streams vendor-CLI agent-layer content into the assistant text instead of model output:

  1. An MCP "lazy-loading" <system-reminder> block that lists the local machine's MCP servers (with the mcp_list / mcp_get / mcp_call meta-tool description), including server names from both ~/.qoder/mcp.json and plugin-provided ones.
  2. Qoder-runtime tool-call markup — three parallel invocations of the shape:
<functions.exec>
<parameter name="cmd">cd <local-repo> && git status</parameter>
</invoke>

Note the mismatched </invoke> closer, consistent with model-emitted text rather than a vendor serializer.

Expected instead: the qoder route is documented as a text/reasoning-only surface (tools disabled, Codex owns tool control); none of this scaffold should reach the client.

Reproduction

  1. Configure the provider: adapter qoder, baseUrl https://qoder.com, authMode key (official PAT).
  2. Route an agent-style Codex turn through it — e.g. ask Codex to check git status of a few local repos.
  3. Observe the assistant text in Codex containing the vendor <system-reminder> ("MCP lazy-loading is active", "## Connected MCP servers") and <functions.exec> markup.

Intermittent — not every turn. Turns that are plain Q&A are less likely to trigger it than agent-style prompts.

Version

2.49.0

Operating system

macOS (Darwin 25.6.0, arm64), Node v24.18.0

Provider and model

qoder

Logs or error output

Screenshots and supporting files

  • src/adapters/qoder/adapter.ts spawns the vendor CLI with --tools "" --strict-mcp-config --setting-sources "" --max-turns 1 --no-session-persistence, and src/adapters/coding-agent/protocol.ts documents "Codex retains tool control; vendor tools are never invoked". The route is contractually a pure text/reasoning surface.
  • The installed @qoder-ai/qodercli@1.1.48 bundle still contains the MCP reminder builder (## Connected MCP servers, ## Available tools, mcp_list), and the leaked reminder listed the local machine's MCP servers — i.e. the spawned CLI appears to initialize its MCP/agent layer despite those flags.
  • The parser maps only text_delta / thinking_delta / tool_use frames and forwards text deltas verbatim, so vendor scaffold content inside a text frame reaches the client unfiltered.
  • Suggested direction: either make the CLI truly run with MCP/tools disabled, or have the adapter detect and sanitize (or fail closed on) vendor scaffold content — system-reminder blocks and framework tool-call markup — in the text channel.

Redacted configuration

{
  "providers": {
    "qoder": {
      "adapter": "qoder",
      "baseUrl": "https://qoder.com",
      "authMode": "key",
      "apiKey": "***"
    }
  }
}

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. added
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    toolstool_calls, MCP, web-search / sidecar tools
    on Sep 10, 2026
  2. lidge-jun commented on Sep 10, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 62 / 80

    설명

    이 이슈는 Qoder(Global) 어댑터로 에이전트형 턴을 돌릴 때, 모델 답이 아니라 벤더 CLI의 에이전트 스캐폴딩이 가끔 어시스턴트 텍스트로 흘러 나온다는 보고입니다. 예시로는 MCP lazy-loading <system-reminder>(로컬 MCP 서버 목록·mcp_list/mcp_get/mcp_call 설명)와, 짝이 안 맞는 </invoke>로 끝나는 <functions.exec> 마크업입니다. 일반 Q&A보다 “git status 같은 에이전트 프롬프트”에서 더 잘 난다고 합니다.

    지금 dev의 계약은 명확합니다. src/adapters/qoder/adapter.ts의 buildQoderArgs는 --tools "", --strict-mcp-config, --setting-sources "", --max-turns 1, --no-session-persistence로 단발·도구 비활성 CLI를 띄웁니다. 주석도 “Codex remains the tool owner”입니다. src/adapters/coding-agent/protocol.ts와 turn.ts도 “Codex retains tool control; vendor tools are never invoked”, 텍스트/추론만 나온다고 적습니다. 그런데 파서는 text_delta/thinking_delta/tool_use 프레임을 매핑하고, text_delta 내용은 그대로 클라이언트에 넘깁니다. 벤더가 MCP 리마인더나 도구 마크업을 텍스트 프레임에 넣으면 걸러지지 않습니다.

    리포터가 지적한 것처럼 설치된 @qoder-ai/qodercli 쪽에 MCP reminder 빌더가 남아 있고, 플래그를 줘도 에이전트/MCP 레이어가 일부 초기화되는 것처럼 보입니다. 그러면 OpenCodex 쪽 구멍은 두 층입니다. (1) CLI가 정말 tools/MCP를 끈 상태로 뜨는지 확인·강화, (2) 그래도 텍스트로 새면 어댑터가 감지해 지우거나 fail-closed 할지. 이 문제는 types.ts/config.ts 분할 캠페인과 무관해서 닫고 리베이스할 대상이 아닙니다. provider/tools 스택의 계약 위반 버그로 보는 게 맞습니다.

    현재 HEAD tip은 Cursor ACP 조사 문서(#4186)이고 Lane A/B 제품 코드는 그대로입니다. Qoder 코딩-에이전트 경로는 살아 있는 통합이라, 스캐폴딩 유출은 “문서상 text-only”와 실제 체감이 어긋나는 신뢰 문제입니다. 우선순위는 전 사용자 스트리밍 장애(#4191)보다 낮지만, Qoder를 쓰는 사람에겐 바로 보이는 오염이라 방치하면 안 됩니다.

    src/adapters/qoder/adapter.ts 라인 20-24 - tools/MCP를 끄려고 플래그를 넣었지만, 리포트상 CLI가 그래도 MCP 리마인더를 만든다. 플래그만으로 계약이 지켜지지 않을 수 있다.
    src/adapters/coding-agent/protocol.ts 라인 247-251 - text_delta를 검사 없이 그대로 text_delta 이벤트로 올린다. <system-reminder>나 <functions.exec>가 텍스트면 클라이언트까지 간다.
    src/adapters/coding-agent/protocol.ts 라인 235-236 - system/init 같은 프레임은 버리고, 텍스트 안에 섞인 스캐폴딩은 버리지 않는다. 프레임 타입과 본문 내용의 방어 깊이가 다르다.
    src/adapters/coding-agent/protocol.ts 라인 268-276 - tool_use 블록은 tool_call_start로 매핑한다. 계약상 벤더 도구는 없어야 하는데, 마크업이 텍스트로 새면 Codex 도구 UI와 섞여 더 헷갈린다.
    src/adapters/coding-agent/turn.ts (심볼 runCodingAgentTurn) - 공통 코딩-에이전트 턴이 “tools disabled → text/reasoning only”를 전제로 한다. Qoder만의 sanitize가 없으면 같은 가정이 깨진 채 흘러간다.

    메인테이너의 판단이 필요한 지점

    • 1차 수정을 CLI 플래그/환경 강화(진짜 MCP off)에 둘지, 어댑터 sanitize(또는 fail-closed)에 둘지, 둘 다 할지.
    • sanitize 범위를 Qoder만으로 할지, coding-agent 공통으로 할지(다른 벤더 CLI에도 같은 유출이 있는지).
    • 유출을 보이면 에러로 막을지(fail-closed), 조용히 지우고 텍스트만 남길지.
    • 재현이 간헐적이므로, 최소 재현 픽스처(리마인더 문자열·functions.exec 샘플)를 골든 테스트로 받을지.

    너의 추천
    열어 두고 provider/tools로 둔다. 바로 할 일은 두 갈래다. (1) buildQoderArgs/자식 env로 MCP·agent scaffold가 실제로 꺼지는지 버전별로 확인하고, 안 꺼지면 벤더 이슈로도 올린다. (2) protocol.ts의 text 경로에 <system-reminder>·<functions.exec> 같은 벤더 스캐폴딩 탐지 후 drop 또는 fail-closed를 넣고, 샘플 픽스처 테스트를 붙인다. types/config 분할 때문에 닫지 말 것. 중복 이슈 없으면 유지.

    이 댓글은 grok-bot이 작성했습니다

  3. lidge-jun commented on Sep 11, 2026

    @lidge-jun
    Owner

    Landed via #4234 at 0589579

  4. lidge-jun commented on Sep 11, 2026

    @lidge-jun
    Owner

    Closed: fix is on dev via #4234 (0589579b7).

  5. lidge-jun commented on Sep 11, 2026

    @lidge-jun
    Owner

    Fixed on dev by #4234.

    The qoder adapter now wraps its emit path with a scaffolding guard. A complete
    <system-reminder> block is stripped before anything reaches the client, and <functions.,
    <invoke>, or a stray </invoke> / </system-reminder> fails the turn closed rather than passing
    markup through as assistant text. That is the second of the two options this issue offered —
    sanitize, or fail closed — and it holds even when the vendor CLI changes the exact shape of its
    scaffolding, because an unrecognized fragment fails rather than leaks.

    One vendor-side leftover is recorded rather than fixed: the CLI can still initialize its MCP layer
    despite --tools "". That happens inside the vendor process and no longer reaches routed output, so
    it is not this issue.

    Closing as fixed. If scaffolding reaches your client again, please reopen with the raw routed
    response so the guard can be extended to that shape.

  6. added a commit that references this issue on Sep 11, 2026
    785a3a2
  7. added 4 commits that reference this issue on Sep 17, 2026
    1949869
    891c382
    d1165b1
    15228cc
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 workinglanded-via-maintainerOriginal PR closed after landing via a maintainer merge trainproviderProvider 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