Repository navigation
Ministral 3 support: error "Only user, assistant and tool roles are supported, got system" #5034
Description
Description
I wish to use opencode with Ministral 3 14B which was recently released. However, opencode raises an error immediately:
Only user, assistant and tool roles are supported, got system
To understand it better, here is first my setup, and how to reproduce the error, which I expect will occur even for the Ministral 3 3B, as we will see later.
I run the server using weights downloaded from unsloth:
huggingface-cli download unsloth/Ministral-3-14B-Reasoning-2512-GGUF Ministral-3-14B-Reasoning-2512-Q5_K_M.gguf
docker run --gpus all -v /data/ml/models/gguf:/models -p 11432:8080 ghcr.io/ggml-org/llama.cpp:full-cuda -s --host 0.0.0.0 -m "/models/Ministral-3-14B-Reasoning-2512-Q5_K_M.gguf" --ctx-size 0 -ub 2048 -b 2048 --n-gpu-layers 99 -fa on -c 65536I then run opencode with the following configuration, as per the documentation:
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"llama.cpp": {
"npm": "@ai-sdk/openai-compatible",
"name": "llama-server (local)",
"options": {
"baseURL": "http://127.0.0.1:11432/v1"
},
"models": {
"ministral-3-14b": {
"name": "Ministral 3 14B (local)"
}
}
}
}
}Then I request anything, and get the error pasted at the top.
The reason I get this error is that llama.cpp relies on the Jinja template inset in the GGUF, which it copied from the original Ministral repository. It loops over loop_messages, expecting not to have system messages. It only expects a system message on the first message.
However, that clearly is not the case with opencode, which can seemingly have up to 3 system messages: https://github.com/sst/opencode/blob/ee4437ff32fc2acbd2220060fc980a096730bcee/packages/opencode/src/session/prompt.ts#L651-L660
For opencode to support Ministral, it needs a single system prompt with the current Jinja template, concatenating them.
OpenCode version
1.0.129
Steps to reproduce
I run the server using weights downloaded from unsloth:
huggingface-cli download unsloth/Ministral-3-14B-Reasoning-2512-GGUF Ministral-3-14B-Reasoning-2512-Q5_K_M.gguf
docker run --gpus all -v /data/ml/models/gguf:/models -p 11432:8080 ghcr.io/ggml-org/llama.cpp:full-cuda -s --host 0.0.0.0 -m "/models/Ministral-3-14B-Reasoning-2512-Q5_K_M.gguf" --ctx-size 0 -ub 2048 -b 2048 --n-gpu-layers 99 -fa on -c 65536I then run opencode with the following configuration, as per the documentation:
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"llama.cpp": {
"npm": "@ai-sdk/openai-compatible",
"name": "llama-server (local)",
"options": {
"baseURL": "http://127.0.0.1:11432/v1"
},
"models": {
"ministral-3-14b": {
"name": "Ministral 3 14B (local)"
}
}
}
}
}Then I enter a message, and I see the error:
Only user, assistant and tool roles are supported, got system
Screenshot and/or share link
Operating System
Ubuntu 24.04.2 LTS
Terminal
Alacritty
Activity
github-actions commentedon Dec 4, 2025
This issue might be a duplicate of existing issues. Please check:
All three issues stem from the same underlying problem: OpenCode generates up to 3 system messages (as referenced in prompt.ts:651-660), but many model providers and Jinja templates expect only a single system message, especially at the start of conversation.
Feel free to ignore if your case differs, but these likely share the same root cause and solution.
arkamar commentedon Dec 5, 2025
This one might be related to ggml-org/llama.cpp#17700 and ggml-org/llama.cpp#17713.
EDIT: actually I am no longer able to reproduce this:
rekram1-node commentedon Dec 7, 2025
I think this is an issue on llama.cpp side right
arkamar commentedon Dec 7, 2025
Well, I don't know. The problem is in default ministral jinja template https://huggingface.co/mistralai/Ministral-3-3B-Reasoning-2512/blob/f8aa436a36b5bf3c59e3c2e8b19368de9cf69914/chat_template.jinja, which basically requires alternating roles in messages. Therefore:
{ ... "messages": [ { "role":"system", "content":"You are" }, { "role":"system", "content":"Here is" }, { "role":"user", "content":"test" } ], ... }and
{ ... "messages": [ { "role":"system", "content":"You are" }, { "role":"user", "content":"test 1" } { "role":"user", "content":"test 2" } ], ... }are both problematic. The first snippet is what opencode basically sends by default and the template throws an error:
Only user, assistant and tool roles are supported, got system. at row 124, column 111...for it. The issue seems to be related to #2272.bamer commentedon Dec 10, 2025
Still not working, everything is fine on the llame.cpp side since they today but it's still not working with opencode :( and devstrall too ....Is there a quick bypass or fix ?
kurnevsky commentedon Dec 10, 2025
For quick fix you can merge all system prompts into one here, or modify mistral chat template and pass it via
--chat-template. Would be good if opencode allowed to customize this.grigio commentedon Dec 12, 2025
A workaround is to use Mistral Vibe, but I hope opencode will support those models too
rekram1-node commentedon Dec 16, 2025
I think this should be fixed? The system prompts should be sent as merged (only exception currently is if u are using anthropic)
haydonryan commentedon Dec 30, 2025
I'm getting this issue., with opencode 1.0.218 using devstral-2-small.
errors from llama-server
srv operator(): got exception: {"error":{"code":500,"message":"After the optional system message, conversation roles must alternate user and assistant roles except for tool calls and results. at row 189, column 164:\n {%- if (message['role'] == 'user') != (ns.index % 2 == 0) %}\n
{{- raise_exception('After the optional system message, conversation roles must alternate user and assistant roles except for tool calls and results.') }}\n ^\n
{%- endif %}\n at row 189, column 13:\n {%- if (message['role'] == 'user') != (ns.index % 2 == 0) %}\n {{- raise_exception('After the optional system message, conversation roles must alternate user and assistant roles except for tool calls and results.') }}\n ^\n {%- endif %}\n at row 188, column 69:\n {%- if message.role == 'user' or (message.role == 'assistant' and (message.tool_calls is not defined or message.tool_calls is none or message.tool_calls | length == 0)) %}\n {%- if (message['role'] == 'user') != (ns.index % 2 == 0) %}\n
^\n {{- raise_exception('After the optional system message, conversation roles must alternate user and assistant roles except for tool calls and results.') }}\n at row 188, column 9:\n {%- if message.role == 'user' or (message.role == 'assistant' and (message.tool_calls is not defined or message.tool_calls is none or message.tool_calls | length == 0)) %}\n {%- if (message['role'] == 'user') != (ns.index % 2 == 0) %}\n ^\n
{{- raise_exception('After the optional system message, conversation roles must alternate user and assistant roles except for tool calls and results.') }}\n at row 187, column 176:\n{%- for message in loop_messages %}\n {%- if message.role == 'user' or (message.role == 'assistant' and (message.tool_calls is not defined or message.tool_calls is none or message.tool_calls | length == 0)) %}\n ^\n
{%- if (message['role'] == 'user') != (ns.index % 2 == 0) %}\n at row 187, column 5:\n{%- for message in loop_messages %}\n {%- if message.role == 'user' or (message.role == 'assistant' and (message.tool_calls is not defined or message.tool_calls is none or message.tool_calls | length == 0)) %}\n ^\n {%- if (message['role'] == 'user') != (ns.index % 2 == 0) %}\n at row 186, column 36:\n{%- set ns = namespace(index=0) %}\n{%- for message in loop_messages %}\n ^
\n {%- if message.role == 'user' or (message.role == 'assistant' and (message.tool_calls is not defined or message.tool_calls is none or message.tool_calls | length == 0)) %}\n at row 186, column 1:\n{%- set ns = namespace(index=0) %}\n{%- for message in loop_messages %}\n^\n {%- if message.role == 'user' or (message.role == 'assistant' and (message.tool_calls is not defined or message.tool_calls is none or message.tool_calls | length == 0)) %}\n at row 1, column 30:\n{#- Unsloth template fixes #}\n
^\n{%- set yesterday_day = strftime_now("%d") %}\n","type":"server_error"}}
(didn't put it in a code block as it tried to make it one really long line)
lee-b commentedon Jan 27, 2026
devstral 2 also affected :/
lee-b commentedon Jan 27, 2026
I think it's desirable that all models would support non-alternating interactions, but that's probably an assumption too far on opencode's part, since the models are usually trained for back-and-forth dialog. Evidence would be that mistral vibe works, opencode does not.
A quick fix might be to join contiguous responses from either party.
7 remaining items
cyangle commentedon Feb 27, 2026
It looks like opencode constructs 2 system messages by default, and qwen 3.5's default template expects 1 system message at the beginning only.
opencode/packages/opencode/src/session/llm.ts
Lines 83 to 93 in f2100dc
paulgear commentedon Feb 27, 2026
I made a fix for this at #15018 - maybe if we can get some 👍 on it it might get merged.
0xDEADBEEF-td commentedon Mar 3, 2026
Thanks for this @paulgear - I built with your fix and I can finally use opencode again with the new Qwen 3.5 models.
Would love to run a release from anomalyco, though. Will keep an eye on this to see when your fix gets merged.
Thanks again!
For non-anthropic providers, combine system prompts
Thump604 commentedon Mar 13, 2026
Reproducing with:
The root cause is that OpenCode sends multiple system messages in its API requests. The Qwen 3.5 official chat template (
chat_template.jinjashipped with the model weights) explicitly rejects this at line 84-85:The template only allows 4 roles:
system,user,assistant,tool. Any other role (likedeveloper) hits the catch-all at line 143-144:This affects ALL Qwen 3.5 models (27B, 35B, 122B) across ALL servers that use the model's built-in template (vllm-mlx, vllm, llama.cpp without custom template override).
Server-side workaround (custom template) exists but shouldn't be necessary — OpenCode should consolidate system messages for non-Anthropic providers as PR #15018 proposes.
+1 for PR #15018 — this is a critical blocker for Qwen 3.5 users.
jacekpoplawski commentedon Mar 13, 2026
Maybe the new issue should be created because it’s not just about Ministral, it’s a much broader problem.
github-actions commentedon Jul 3, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.