anomalyco / anomalyco/opencode

deepseek.v3.2 on amazon-bedrock: long --auto sessions intermittently end with a complete DSML tool call left as plain text (finish reason 'stop')

Open
#45,600 1 comment 1 reaction 1 assignee View on GitHub

@rekram1-node is already working on this.

Since Aug 27, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Summary

On amazon-bedrock/deepseek.v3.2, a long opencode --auto session intermittently ends mid-task: the assistant's final message contains a complete, well-formed DeepSeek DSML tool-call block as plain text, the step finishes with reason stop, and no tool part is produced. The session then sits idle with the task unfinished.

Reproduced 1 time in 3 at ~40 sequential tool calls. Reporting observed behaviour only — I have not tried to apportion this between the Bedrock serving layer and opencode's adapter, and the data below is consistent with either owning part of it.

Environment

  • opencode 1.18.23 (latest release at time of testing)
  • provider amazon-bedrock, model deepseek.v3.2, region eu-west-2, AWS SSO auth
  • Linux (WSL2)

Repro

Prompt asks for N sequential one-per-step bash calls, each writing a distinct nonce file, then a final count:

Step 1: run  echo step-1 > <dir>/step-1.txt
Step 2: run  echo step-2 > <dir>/step-2.txt
... (40 steps)

Run as opencode --auto --model amazon-bedrock/deepseek.v3.2 --prompt "<the above>" under a pty. Count the nonce files that appear.

entry point sessions outcome
opencode --auto (TUI) 3 × 40 steps 1 stopped at step 32/40; 2 completed
opencode run 3 (22, 40, 61 tool calls) all completed

What the stalled session contains

Final three parts of the failing session, from the session store:

{"reason": "tool-calls", "type": "step-finish", "tokens": {"total": 26118, "input": 25749, "output": 369}}
{"type": "step-start"}
{"type": "text", "text": "\n\n<|DSML|function_calls\n<|DSML|invoke name=\"bash\">\n<|DSML|parameter name=\"command\" string=\"true\">echo step-32 > <dir>/step-32.txt</|DSML|parameter>\n</|DSML|invoke>\n</|DSML|function_calls>"}
{"reason": "stop", "type": "step-finish", "tokens": {"total": 26224, "input": 26137, "output": 87}}

So the intended call is fully formed and unambiguous — it just arrives as text, and --auto reasonably treats a no-tool-call message as the final answer.

Related, and present in every tool-calling turn on this model: the text block always ends with a bare \n\n<|DSML|function_calls fragment alongside a correct tool call. In the failing session, 35 of 36 text parts carry one.

Counter-evidence: the raw Bedrock API looked clean

To check whether the serving layer alone explains it, I drove bedrock-runtime directly with boto3, same model and region, classifying every turn as native toolUse / DSML-in-text / plain text:

shape calls native toolUse call landed as text
converse, 20 × 1 turn 20 20 0
converse-stream, 20 × 1 turn 20 20 0
converse-stream, 5 × 6 turns 30 30 0
as above + 10 tools and a long system prompt 30 30 0
converse-stream, 1 × 30 turns 30 30 0
converse-stream, 1 × 45 turns, padded tool results (~26k-token context) 45 45 0

175/175 returned a native toolUse block. All 175 also carried the same trailing <|DSML|function_calls fragment in the text, so the leak reproduces at the API level — but the whole-call-as-text failure did not, including at the turn depth and context size where the real session broke.

The OpenAI-compatible endpoint (/openai/v1/chat/completions) shows the identical leak: correct tool_calls array, and "content" ending in ...\n\n<|DSML|function_calls.

Why I am raising it here

I could not reproduce the fatal case outside opencode, and it appeared only on the --auto TUI path, so I cannot rule out something about how that path builds requests or handles the assistant history. My synthetic API loop feeds the leaked text fragment back verbatim and still never fails, but it obviously does not replicate everything opencode sends.

Two things maintainers may find worth considering, offered as options rather than a diagnosis:

  1. A fallback parse for in-band DSML markup when a message finishes with stop and contains a complete tool-call block — the information needed to recover the call is fully present.
  2. Failing that, treating "final message contains unparsed tool-call markup" as a loud error rather than a normal completion, so it does not read as a finished task.

Downstream context, if useful: dare-global/conductor#2024. We have blocked the model for agentic use on our side meanwhile.

I can supply the full session parts or the API soak harness if either helps.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.