Skip to content

[codebuddy] Routed tool-call markup reaches the client as assistant text (no scaffolding guard, unlike #4190) #4596

Description

@xiaoshuai7038

Client or integration

Codex App

Area

Provider adapter

Summary

Routing an agent-style turn through the codebuddy adapter streams the routed model's raw tool-call markup into the assistant text instead of model output. The markup arrives as plain assistant text, not as a typed tool_use frame.

The route launches the vendor CLI with tools and MCP disabled, so the routed model has no native tool channel to put a call in. It writes the call spelling as prose, and content_block_delta text frames are forwarded to the client unrepaired - the same one line of trust #4190 found for qoder:

  • src/adapters/coding-agent/protocol.ts:175 - the text_delta emit on the full-message path
  • src/adapters/coding-agent/protocol.ts:251 - the same emit inside the content_block_delta branch

CodeBuddy runs the same runCodingAgentTurn as Qoder, and #4234 (0589579b7) added src/adapters/qoder/scaffold-guard.ts for qoder only. src/adapters/codebuddy/ on dev still contains just adapter.ts and profiles.ts, and src/adapters/coding-agent/ has no filter.

Expected instead: the codebuddy route is the same tools-disabled, text-only surface as qoder. Either the call spelling should be turned into a real tool call (the adapter's own comment calls the control-protocol tool bridge a fast-follow), or the markup should fail closed the way it does for qoder.

Reproduction

  1. Configure a provider that uses adapter codebuddy (here: codebuddy-cn).
  2. Route an agent-style Codex turn through it - e.g. ask Codex to inspect git state and read named files in a local repo.
  3. Observe the assistant text in Codex containing the markup in the log section below.

Intermittent, but it hit 2 of the 2 consecutive agentic turns in the captured session, roughly 35 seconds apart.

Version

2.54.0

Operating system

macOS 26.5.1 (Darwin 25.5.0, arm64), Node v26.5.0

Provider and model

codebuddy (provider id codebuddy-cn) / deepseek-v4.1-flash

Logs or error output

Both blocks are assistant output_text verbatim, captured on the published 2.54.0 build. Two consecutive turns: 2026-09-14T00:42:51Z and 2026-09-14T00:43:26Z (08:42:51 and 08:43:26 CST). Local absolute paths are replaced with [local-path].

Turn 1:

I'll start with a read-only reconnaissance pass: git state, the docs you named, and the actual Android recording/upload code paths.

<||DSML|| calls>
<||DSML|| invoke name="functions.exec">
<||DSML|| parameter name="command" string="true">cd [local-path] && git status --short --branch && echo "---HEAD---" && git rev-parse HEAD && echo "---REMOTE---" && git rev-parse origin/main 2>/dev/null && echo "---WT---" && git worktree list --porcelain | head -40</||DSML|| parameter>
</||DSML|| invoke>
</||DSML|| calls>

Turn 2:

I'll run the real commands now.

<||DSML|| calls>
<||DSML|| invoke name="functions.exec">
<||DSML|| parameter name="cmd" string="true">cd [local-path] && git status --short --branch && echo "---HEAD---" && git rev-parse HEAD && echo "---ORIGIN_MAIN---" && git rev-parse origin/main</||DSML|| parameter>
</||DSML|| invoke>
</||DSML|| calls>

Notes on the shape:

  • Every tag is wrapped in full-width bars (U+FF5C), not ASCII pipes. The opener is <||DSML|| invoke name="functions.exec">, where those bars sit between the angle bracket and the tag name. None of the UNREPAIRABLE_MARKERS shipped for qoder match this leak - see the verification comment below. A signature extension is needed, not only a port of the existing filter.
  • Turn 2's parameter body is empty, and the surrounding prose ("I'll run the real commands now.") reads as agent narration rather than the model's answer.
  • opencodex usage rows for the same window report provider codebuddy-cn, model deepseek-v4.1-flash, status 200, inbound protocol responses.
  • Both turns ran against the freshly installed published build: the 2.50.0 -> 2.54.0 update finished at 2026-09-14T00:33Z (~/.opencodex/update-job.json), about nine minutes earlier, and no local modification had been applied to the package.

Screenshots and supporting files

  • src/adapters/codebuddy/adapter.ts builds args with --tools "" and --strict-mcp-config, so no native tool channel is open, and its own doc comment records: "Codex's tool catalog is not advertised in v1 (the control-protocol tool bridge is a fast-follow)."
  • src/adapters/coding-agent/protocol.ts filters frame types but never inspects frame contents before emitting text_delta.
  • The lane packet for [qoder] Vendor CLI agent scaffolding leaks into routed output (MCP lazy-loading reminder + tool-call markup) #4190 anticipated this report (devlog/_plan/260911_l6_streaming_tools/020_4190_qoder_scaffold_guard.md): "Qoder only, not the shared coding-agent path. ... If CodeBuddy turns out to do the same thing, the filter is a pure module and lifting it is a small change - but it should be driven by a report, not by symmetry."
  • Source session log: ~/.codex/sessions/2026/09/14/rollout-2026-09-14T08-42-42-01a09d5d-c356-7b82-9d40-bb70dba9d487.jsonl, response_item -> message, role assistant, content[0].type == "output_text", lines 13 and 26.

Checks

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

Activity

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