[Bug]: gpt-oss (HarmonyParser): a stray 'commentary to=assistant' header is returned as a tool call named 'assistant<|channel|>analysis'
Your current environment
Environment (collect_env.py asserts on macOS, so summarised by hand)OS: macOS 26.5.1 arm64, Python 3.12.13, CPU only
vLLM: 0.30.0 (pure-Python parser code from the manylinux wheel), and main @ 3b4566c5cf014605de6aeab6eb831b4f20511c17
openai-harmony 0.0.8, transformers 5.17.0, torch 2.14.0 (CPU)Parser-only bug: no GPU, weights or server needed. Tokenizer: openai/gpt-oss-20b@6cee5e81.
Describe the bug
gpt-oss-20b sometimes opens a turn with a stray <|channel|>commentary to=assistant header before its analysis channel. llama.cpp added a rule for it (ggml-org/llama.cpp#21286: "not defined in the Harmony docs, but appears to be a common generation from the 20b variant"). vLLM's Chat Completions HarmonyParser turns that header into a tool call named assistant<|channel|>analysis. The call's arguments are the model's reasoning, and the name is not one of the request's tools.
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
from vllm.parser.parser_manager import ParserManager
from vllm.tokenizers import get_tokenizer
tok = get_tokenizer("openai/gpt-oss-20b", revision="6cee5e81ee83917806bbde320786a8fb61efebee")
tools = [{"type": "function", "function": {"name": "get_weather", "parameters": {
"type": "object", "properties": {"city": {"type": "string"}}}}}]
req = ChatCompletionRequest(model="m", messages=[{"role": "user", "content": "hi"}],
tools=tools, tool_choice="auto")
cls = ParserManager.get_parser("openai", "openai_gptoss", enable_auto_tools=True, is_harmony=True)
out = ("<|channel|>commentary to=assistant<|channel|>analysis<|message|>I'm\nthinking<|end|>"
"<|start|>assistant<|channel|>final<|message|>Hello, world!\nWhat's up?")
ids = tok.encode(out, add_special_tokens=False)
reasoning, content, calls = cls(tok, req.tools, model_config=None).parse(
tok.decode(ids, skip_special_tokens=False), req, enable_auto_tools=True, model_output_token_ids=ids)
print("reasoning :", repr(reasoning))
print("content :", repr(content))
print("tool_calls:", [(c.name, c.arguments) for c in (calls or [])])Output on 0.30.0 and on main @ 3b4566c (identical):
reasoning : None
content : "Hello, world!\nWhat's up?"
tool_calls: [('assistant<|channel|>analysis', "I'm\nthinking")]Streaming (parse_delta, with one token per delta, the whole output in one delta, or random groups) gives the same tool call.
Expected: no tool call. llama.cpp's test for this string (test-chat.cpp L6540-L6546) recovers reasoning="I'm\nthinking" and content="Hello, world!\nWhat's up?". Keeping the content and dropping the reasoning would also be reasonable. A tool call is not.
Where: vllm/parser/harmony.py, _SegmentType.from_channel_and_recipient. It treats any recipient accepted by is_function_recipient as a tool call. On the Chat Completions path that means every bare recipient. The recipient here is assistant<|channel|>analysis: to=assistant addresses the assistant, not a function, and the embedded <|channel|> marker ends up in the name. _normalize_recipient already strips a misparsed <|constrain|> from recipients. Handling <|channel|> the same way, and not treating an assistant recipient as a function, would probably fix this.
Related: #32587 reports <|channel|> leaking into gpt-oss tool names (for example ls<|channel|>commentary) in multi-turn use. This is a single-turn, parser-only repro of the same symptom, and may share a fix. #35906 sanitises recipients on the Responses API path.
Fixture: gpt-oss/llamacpp-stray-commentary-header
I found this with an automated conformance harness. The report was drafted with AI assistance, and I verified the repro above by running it on both versions.
Found by canitoolcall (https://github.com/redd34/canitoolcall), an offline conformance suite for tool-call parsers.
Before submitting a new issue...
- Make sure you already searched for relevant issues, and asked the chatbot living at the bottom right corner of the documentation page, which can answer lots of frequently asked questions.
Source: vllm-project/vllm