anomalyco / anomalyco/opencode
Unimplemented `commentary` channel in gpt.txt: progress updates end the turn on chat-completions models
@neriousy is already working on this.
Since Sep 3, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Description
opencode's GPT system prompt tells the model to send short progress updates on a commentary channel. Nothing in opencode implements that channel, and on chat-completions providers each such update ends the turn.
packages/opencode/src/session/prompt/gpt.txt:81-97 describes a commentary channel for progress updates and a final channel for the answer; :95 says "Before substantial work, send a short update describing your first step." At dev@d2efd81fb the word appears in two prompt text files and zero .ts/.tsx/.js files.
The prompt is picked by model.api.id.includes("gpt") (packages/opencode/src/session/system.ts:34, PROMPT_GPT at :38), so it also goes to plain chat-completions models. That path has no channel field: packages/llm/src/protocols/openai-compatible-chat.ts:17-21 reuses OpenAIChat.protocol against /chat/completions, and the delta schema at openai-chat.ts:145-154 parses only content, reasoning_content, tool_calls and finish_reason. A commentary update arrives as ordinary assistant text with finish_reason: "stop" and no tool parts.
That matches the loop-exit condition in packages/opencode/src/session/prompt.ts:1113: a finished assistant message with no tool parts is treated as completion. So a progress update requested by gpt.txt:95 can terminate the turn before tools run.
// packages/opencode/src/session/prompt.ts:1106-1117
const hasToolCalls = lastAssistantMsg?.parts.some(
(part) => part.type === "tool" && !part.metadata?.providerExecuted && !isOrphanedInterruptedTool(part),
) ?? false
if (
lastAssistant?.finish &&
!["tool-calls", "unknown"].includes(lastAssistant.finish) &&
!hasToolCalls &&
lastAssistant.parentID === lastUser.id
) {
// ...
yield* Effect.logInfo("exiting loop", { "session.id": sessionID }) // :1128
break
}
Over one long chat-completions session: 1196 turns finished tool-calls, all carrying tool parts; 115 finished stop, all text-only; the log holds exactly 115 exiting loop lines for that session. Every text-only stop turn I inspected there had a matching exiting loop line.
This is not the finish: "unknown" family (#43622, #41469, #37852; #44146 is a generic premature-stop report the duplicate bot grouped there). Those turns have no usable finish reason and zero tokens, and "unknown" is already excluded at :1113. Here stop is honest and the message has real content — the guard does what it says, and the problem is upstream of it, in the prompt.
Fix: only emit the gpt.txt:81-97 block on a transport that implements channels — system.ts:27-48 dispatches on model id alone. Short-term mitigation: remove or gate the commentary instruction until a transport path supports it.
Search output, and the Responses side
$ rg -c -i 'commentary' . -g '!**/node_modules/**' -g '!**/dist/**' -g '!.git/**'
./packages/opencode/src/session/prompt/gpt.txt:5
./packages/opencode/src/agent/generate.txt:4
$ rg -n -i 'commentary' packages/ -g '*.ts' -g '*.tsx' -g '*.js' -g '!**/node_modules/**' -g '!**/dist/**'
(no output)
generate.txt's hits are <commentary> XML tags in few-shot examples for agent generation, not a response channel.
The Responses API does carry a phase field on message items, and recorded fixtures under packages/llm/test/fixtures/recordings/openai-responses/ contain "phase":"final_answer" — so providers send it. opencode drops it: OpenAIResponsesStreamItem (packages/llm/src/protocols/openai-responses.ts:177-198) enumerates item fields without phase, and rg -w -i 'phase' packages/llm/src is empty. (phase does occur elsewhere in the tree — cli/cmd/run/*, UI and e2e code — but that is opencode's own scrollback/UI lifecycle state, never parsed from a model-emitted field.)
Plugins
None. (Default configuration, no plugins loaded.)
OpenCode version
v1.18.27 (desktop Electron build; the bundled server reports 1.18.26).
Steps to reproduce
Reproducible against a local stub provider — no real model needed.
- Configure an OpenAI-compatible chat-completions model whose id contains
gptand does not match earlier prompt branches (gpt-4,o1,o3,codex), e.g.gpt-5.5.system.ts:32-33pre-emptsgpt-4/o1/o3toPROMPT_BEASTand:35-37sendscodexids toPROMPT_CODEX; everything else matchinggptfalls through toPROMPT_GPTat:38. - Point opencode at a local stub endpoint serving that model id.
- Dump the system prompt and confirm the "## Response channels" block from
gpt.txt:81is present. Nothing about the provider's transport is consulted. - Send one prompt that needs several tool calls. Hold everything constant and vary only what the stub streams for the first assistant turn:
| stub streams | tool parts | finish |
result |
|---|---|---|---|
text "Reading the config now.", then finish_reason: "stop" |
none | stop |
guard at prompt.ts:1113 fires, exiting loop logged at :1128, loop breaks at step 1 |
the same text plus one tool_calls delta, then finish_reason: "tool_calls" |
one | tool-calls |
loop continues (control) |
Row 1 is the message shape gpt.txt:95 instructs the model to send before starting work. Row 2 is the only reason the agent normally survives a progress update: the model happened to attach a tool call to it.
Screenshot and/or share link
No share link — the affected sessions run against a self-hosted gateway that is not reachable from /share. The behaviour is reproducible from the stub-provider recipe above without one.
Operating System
Linux (Ubuntu, kernel 6.8), x86_64.
Terminal
Not applicable — observed in the desktop (Electron) app, not a terminal. The mechanism is in the server-side agent loop and is transport-, not terminal-, dependent.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.