Repository navigation
Dead loop: infinite empty assistant messages (tokens=0) after tool call with empty text #28507
Description
Description
After an LLM returns a tool call with empty/no text content, the agent loop enters an infinite cycle creating empty assistant messages (tokens: {input:0, output:0}) without ever invoking the LLM. The only recovery path is manual compaction.
Reproduction
- Start an interactive session with a custom provider (in our case, glm-5 via llm-router)
- LLM returns a tool call (e.g., Read a PNG file) with empty text in the response
- Tool executes successfully, result is sent back to the LLM
- On the next loop iteration, the LLM returns an empty/errored response (
tokens: {input:0, output:0}) - Loop continues indefinitely creating empty assistant messages
Session evidence: ses_1bba6f1c4ffe55rFTzStkU2fyM — 162 total messages, two dead zones:
- msg[143-149]: 7 empty assistant messages, all tokens=0
- msg[155-161]: 7 empty assistant messages, all tokens=0
- Recovery at msg[150] and msg[162] via compaction
Root Cause Analysis
The exit condition (prompt.ts L1268-1276)
The while(true) loop at prompt.ts:1248 breaks only when ALL four conditions are true:
if (
lastAssistant?.finish && // (1) finish is truthy
!["tool-calls"].includes(lastAssistant.finish) && // (2) not tool-calls
!hasToolCalls && // (3) no pending tools
lastUser.id < lastAssistant.id // (4) ID ordering
) {
break
}When the LLM returns an empty response after a tool result, finish may be "unknown" or unset (condition 1 fails), and the loop continues.
Why safety nets don't catch it
-
DOOM_LOOP_THRESHOLD(processor.ts:424) — Only detects 3 consecutive identical tool calls (same tool name + same input). Does NOT catch empty responses or different tools. -
isOverflow()(overflow.ts:20-31) — Whentokens = {input:0, output:0},count = 0, soisOverflow()returnsfalse. The compaction safety net never triggers. -
No max-empty-steps guard — There is no mechanism to detect "N consecutive steps with zero tokens" and force-exit the loop.
Recovery
Only compaction recovers the session — it regenerates context and creates an assistant message with finish: "stop", which passes the exit check.
Suggested Fix
Add a guard in the runLoop to detect consecutive empty responses:
// In the while(true) loop, after processor returns:
if (handle.message.tokens.input === 0 && handle.message.tokens.output === 0) {
emptyStepCount++
if (emptyStepCount >= 3) {
yield* slog.warn("exiting loop: consecutive empty responses")
break
}
} else {
emptyStepCount = 0
}Alternatively, isOverflow() could treat consecutive zero-token responses as a degenerate case that should trigger compaction or loop exit.
Environment
- opencode: v1.15.5 (installed via Homebrew)
- Provider: custom (glm-5 via llm-router, OpenAI-compatible API)
- OS: macOS (darwin)
Related Issues
- Web: client clock skew can cause repeated assistant responses / prompt loop #28339 — Clock skew causes ID ordering failure in the same exit condition
- Streaming response validation fails when provider returns empty
delta.roleinstead of"assistant"#28427 — Provider returns emptydelta.role, stream error preventsfinishfrom being set - Loop Compacted opencode --version 1.15.5 #28426 — Compaction loop issue
Activity
github-actions commentedon May 20, 2026
This issue might be a duplicate of existing issues. Please check:
finish: "unknown"withtokens: {input:0, output:0}, causing an infinite loop in the agent. That issue also uses the zhipuai/glm-5 provider and shows identical session JSON with zero-token messages.If this is a different scenario or a new angle on the same underlying bug, it may be worth cross-referencing or consolidating with #18813.
GonyRosenman commentedon May 31, 2026
I think I hit a related case in interactive TUI, but not an infinite loop.
Environment:
What happened:
The agent successfully completed an edit tool call on:
src\chamber\engine\sound\setups\preview_director_prompt.py
The edit output was “Edit applied successfully.”
Immediately after that, opencode created a new assistant message that finished normally with stop, but produced no visible text and no tool call. The UI looked like the assistant just silently stopped after editing the file.
From opencode.db:
Previous message:
Next assistant message:
The task was not actually finished, the todo table still had pending/in-progress items. So this was not just a cosmetic final empty message, it caused the session to appear to stop mid-task.
This seems related to the empty assistant/tool-call continuation family of bugs, but in my case it was a single silent empty stop after a successful edit, not an infinite empty loop.
github-actions commentedon Jul 31, 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.