Skip to content

2.0.32: tool-call markup written as plain text ends the step with finish=stop — no tool part, intended call lost (2.0 re-file of #43385) #57349

Closed as duplicate

Description

@untitledds

Summary

In Code Mode, the model intermittently writes its tool call as plain
assistant text
instead of emitting a structured tool call. The text uses a
DSML-style dialect — inner parameters and closers are marked |DSML|
(DeepSeek Model Spec Language) while the opening tag often is not:

<parameter name="read">
<|DSML| parameter name="path" string="true">/root/zorq-sigma-bridge/app/api/catalog.py</|DSML| parameter>
<|DSML| parameter name="oldString" string="true">placeholder</|DSML| parameter>
</|DSML| invoke>
</|DSML| calls>

OpenCode does not parse or execute that text: no tool part is created, the
step finishes with reason stop and the session goes idle, and the intended
tool call is silently lost. The run then stalls and only proceeds when another
turn is issued.

Environment

  • opencode version: 2.0.32 (latest release, Oct 16 2026)
  • OS: Linux 6.18.54.1-microsoft-standard-WSL2 x86_64 GNU/Linux
  • Agent: build, Code Mode
  • Provider/model: opencode/big-pickle, variant default
  • Plugins: none (no opencode.json / .opencode/opencode.json)

Reproduction

  1. Run a long multi-step build task in Code Mode.
  2. Observe the last assistant message of a step. Instead of a structured tool
    call it contains prose followed by raw call markup, and no tool part.

Concrete transcript from session ses_ea9cd281cffe9pJbrBsyPQSpup (read-only
query against ~/.local/share/opencode/opencode.db):

10:50:33  assistant  [text, TOOL:grep:completed, TOOL:shell:completed]   <- real calls, fine
10:50:36  assistant  [text*MK*]                                          <- only text, NO tool part
10:50:39  idle                                                           <- step ends
10:52:03  user                                                            <- new turn needed to continue

The text*MK* message body looks like:

Let me look at `get_deployed_stems` and `_refresh_deployed_flag` — this is the core of the bug.
<parameter name="path">/root/zorq-sigma-bridge/app/…

The read it asks for never runs in that step; the same read is repeated and
executed only in the following turn.

finish reason confirms the root cause described in #43385

Every assistant message whose text part carries the markup finishes the step
with reason stop, never tool-calls:

10:40:14 MK  finish=stop       rawFinish=stop        tokens_out=102
10:50:36 MK  finish=stop       rawFinish=stop        tokens_out=117
10:52:10 MK  finish=stop       rawFinish=stop        tokens_out=94
10:53:24 MK  finish=stop       rawFinish=stop        tokens_out=225

while ordinary messages in the same session that make real calls report:

01:05:35 NORMAL finish=tool-calls rawFinish=tool_calls tokens_out=70
01:05:41 NORMAL finish=tool-calls rawFinish=tool_calls tokens_out=80

Aggregate over the three affected sessions (assistant messages whose text part
contains call markup):

{ "stop": 47, "tool-calls": 14 }

The 14 tool-calls cases are mixed messages where a structured call was made
and markup was written alongside it — those steps continue normally and the
markup is only noise. The 47 stop cases are the ones that halt the loop,
because no structured tool call was emitted.

This is exactly the failure mode analyzed in #43385: the call arrives as
text-delta, no tool-call event is produced, step-finish reports stop,
and the agent loop terminates mid-task.

The dialect is DSML, and often self-inconsistent

Counting only assistant text parts in the three affected sessions:

plain "<parameter name=" opens ......... 43
"|DSML|"-marked parts/closers ......... 30
"<invoke" ..............................  9
"antml:" ...............................  0

antml: never appears in model output for these sessions — the dialect is
DSML-shaped, not Anthropic's. And it is frequently malformed: 29 of 43
markup messages close with </|DSML| invoke> / </|DSML| calls> while the
opening <parameter name="…"> carries no |DSML| marker (see the sample in
the Summary), so even a naive text parser would not be able to read them
directly.

Normal structured calls in the same sessions are unaffected — e.g. shell
2046×, read 685×, edit 508× completed — which is why this reads as a
dialect/normalization problem rather than a fault in any individual tool.

Expected Behavior

The intended tool call should not be dropped. Either:

  • parse the text-form call and execute it (a text fallback parser), or
  • immediately re-prompt the model to emit a real tool call instead of ending
    the step and leaving the session idle,

and in all cases surface why the step ended.

Actual Behavior

No tool part is created, the step ends with idle, the call is lost, and the
session needs an extra turn to move on — effectively stalling long agent runs.

Occurrences (text parts containing call markup, excluding transcripts where the
agent itself quotes the markup while discussing it):

session text-parts with markup window
ses_eaea51780ffex7kzsnKf1s1lak 7 19 Oct 23:02
ses_eaa3947b1ffeexwKdX3g4RKT3t (fork of the above) 36 19 Oct 23:02 → 20 Oct 01:04
ses_ea9cd281cffe9pJbrBsyPQSpup (new session) 6 20 Oct 10:40 → 10:54

9 other sessions on the same provider/model/version never produced it, so it
is intermittent and session-dependent.

Additional Context

  • Prompt-level workarounds do not help. A repo AGENTS.md rule was added
    that forbids emitting this markup (initially listing the forbidden tokens as
    examples, then rewritten to avoid mentioning them at all). The rules were
    removed from the instructions before the last reproduction: session
    ses_ea9cd281… was created at 01:05 on 20 Oct, after the instruction files
    were already clean, and still reproduced at 10:40–10:54. Its recorded
    instruction blob contains no markup tokens.
  • Not a skills/plugins issue. All installed skill files were scanned for
    <invoke / <parameter name= / antml: / function_calls — no matches;
    no plugins are configured.
  • The markup always appears in the final assistant message of a step,
    immediately before the idle message, and is interleaved with normal
    structured tool calls earlier in the same step.
  • A separate issue in the same environment is a recurring
    AI.Error: Rate limit exceeded (provider.quota, FreeUsageLimitError) on
    the free tier; it is independent of the markup problem and is not
    contributing to the reports above.

Prior art — #43385 already identified the cause and a fix

#43385 ("MiMo V2.5 outputs tml:function_calls as plain text instead of
structured tool calls") reports the same failure mode and contains a root-cause
analysis that matches this environment one-to-one. It was closed only because it
was filed against OpenCode 1.x, with an explicit invitation to open a new issue
for 2.0:

OpenCode 2.0 is now out and development is focused on V2, so we're closing
issues filed against OpenCode 1. […] if this is still relevant on 2.0,
reopen this issue or open a new one with your 2.0 version. — @thdxr

Root cause as described there (still true on 2.0.32):

  • the runtime only consumes OpenAI-style structured tool_calls
    (delta.tool_calls[].function.{name,arguments});
  • there is no text-based fallback parser, so the call arrives as
    text-delta, no tool-call event is ever emitted, step-finish reports
    stop, and the agent loop stops.

Code paths named there:

  • packages/llm/src/protocols/utils/tool-stream.ts — parses only structured SSE tool_calls
  • packages/opencode/src/session/processor.ts — handles only tool-call / tool-result events
  • packages/opencode/src/session/llm/ai-sdk.ts — toLLMEvents() forwards only structured tool calls

Suggested fix (reproposed for 2.0): when a step finishes with reason stop but
the accumulated text contains call-shaped markup (<invoke name=…>,
<parameter name=…>, |DSML|-marked parameters, <function_calls>, …), parse
that text and emit synthetic tool-call events so execution continues — or at
minimum re-prompt the model for a real tool call instead of ending the step.
Note the opener/closer markers can disagree (see the DSML section above), so the
parser needs to tolerate unbalanced markers rather than requiring an exact
document shape.

Related

Activity

  1. opencode-agent commented on Oct 20, 2026

    @opencode-agent
    Contributor

    Thanks for the extra detail, @untitledds! I think this is a duplicate of #57344, which you opened earlier today. It describes the same problem: in Code Mode on 2.0.32, opencode/big-pickle writes <invoke> / <parameter> markup as plain text, no tool part is created and the step ends idle. It cites the same sessions, and it is already open and triaged.

    The new evidence here (every markup-only message ending with finish=stop / rawFinish=stop, the { stop: 47, tool-calls: 14 } totals, and the pointers to #43385) is useful. It would be best added as a comment on #57344 so everything stays in one place.

    Possible duplicates and related issues, ordered by likelihood:

    The bot will close this issue as a duplicate in 1 day unless you reply explaining how it differs from #57344.

  2. opencode-agent commented on Oct 21, 2026

    @opencode-agent
    Contributor

    Closing this as a duplicate of #57344, since nobody has replied to say how it differs. Both issues describe the same problem: in Code Mode on 2.0.32, opencode/big-pickle writes tool-call markup as plain text, no tool part is created and the step ends idle.

    Please add the extra evidence from this issue to #57344 so it's all in one place. That means the finish=stop / rawFinish=stop breakdown, the { stop: 47, tool-calls: 14 } totals and the #43385 pointers. If this turns out to be a different problem after all, reply here and we'll take another look.

    Thanks for the thorough investigation, @untitledds!

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions