Skip to content

Bug: OpenCode enters infinite loop after tool calls complete (Zen/big-pickle) #26220

Closed as not planned

Description

@Dvalin21

Bug: OpenCode Enters Infinite Loop After Tool Calls Complete

Description

OpenCode (opencode) enters an infinite loop and stops responding to user input after completing tool calls. The process stays alive but never exits or continues meaningfully.

Affected Versions

Symptoms

  1. Process never exits: opencode run hangs indefinitely after tool calls complete
  2. Silent infinite loop: Session processor keeps calling LLM with empty responses
  3. No CPU usage: Process alive at 0-2% CPU, doing nothing useful
  4. No error output: Logs show no errors, no timeouts
  5. Parent sessions block: When used as subagent, parent hangs forever waiting

Root Cause (from issue #17516)

OpenCode doesn't detect "done" state properly.

After the model finishes tool calls and produces final text response:

  1. Session processor should detect finish_reason=stop or "no tool calls in text-only response"
  2. Instead, it treats the response as requiring another iteration
  3. Calls LLM again with same context
  4. Creates infinite loop of empty LLM calls

Timeline from debug logs:

18:53:24 | step=0  | Session created, prompt resolved
18:53:27 | step=1  | Model calls Read/Write → both succeed
18:53:32 | step=2  | LLM stream → NO tool call, NO visible output
18:53:35 | step=3  | Same pattern — empty loop iteration
...
18:55:21 | step=42 | Still looping when killed at 120s

Pattern per step (steps 2-42):

session.prompt step=N
session.prompt status=started resolveTools
tool.registry status=started/completed (all 12 tools)
permission evaluate (task general, task explore)
session.prompt status=completed resolveTools
session.processor process
llm providerID=opencode/big-pickle modelID=zen stream
→ next step (infinite loop)

Related Issues Found

Issue Description Status
#17516 opencode run hangs after completing tool calls Open
#11153 Session loop doesn't stop when model returns finish_reason=stop Closed (fixed in #11152)
#19208 opencode run adds empty stop-turn after tool-calls completion Closed (dup of #17982)
#21250 TUI/task runs stall after last completed tool; child tasks spawn empty assistant run Open
#20096 Tool and task execution can hang indefinitely with no timeout Open
#13841 Explore subagent hangs indefinitely with Anthropic Claude Opus 4.6 Open
#11865 Tasks/Subagents with Codex/OpenAI get stuck with no timeout/retry Open

Reproduction Steps

# Create a prompt that uses tools
cat > /tmp/test-prompt.md << 'EOF'
Read compress.py and replace the RLE implementation with zlib.
Edit the file directly. Do not run any commands.
EOF

# Run with debug logging - using Big Pickle + Zen provider
opencode run --print-logs --log-level DEBUG \
  -m opencode/big-pickle \
  < /tmp/test-prompt.md 2>debug.log

# Process hangs after tool calls complete
# debug.log shows session.prompt stepping infinitely with no tool calls
# LLM provider: opencode/big-pickle with Zen

Expected Behavior

After the model's final text response (post-tool-call), OpenCode should:

  1. Detect that the model has finished (finish_reason=stop or no tool calls)
  2. Exit cleanly with code 0
  3. NOT call LLM again

Actual Behavior

OpenCode:

  1. Treats final response as requiring another iteration
  2. Calls LLM again with same context
  3. Loops infinitely with empty LLM calls every ~2.5s
  4. Never exits, never produces output
  5. Must be killed manually (Ctrl+C or kill -9)

Workarounds

  1. Switch models: Use gpt-5.3-codex instead of claude-sonnet-4.6 (works perfectly)
  2. Switch providers: Use different provider that sends proper stop signals
  3. Manual kill + restart: Ctrl+C and rephrase prompt

Suggested Fixes

Fix 1: Detect finish_reason=stop (Primary)

In processor.ts, the process() function should return "stop" when finish_reason=stop:

// In processor.ts process() function
if (response.finish_reason === 'stop' && !response.tool_calls) {
  return "stop"; // Currently returns "continue"
}

Fix 2: Break on Empty Responses

If LLM is called but produces NO tool calls and NO text, treat as done:

// After LLM stream completes
if (!response.content && (!response.tool_calls || response.tool_calls.length === 0)) {
  logger.info('Empty response detected, stopping loop');
  return "stop";
}

Fix 3: Add Loop Detection

Detect when same prompt step repeats without progress:

// Track consecutive empty responses
let consecutiveEmptyResponses = 0;

if (isEmptyResponse(response)) {
  consecutiveEmptyResponses++;
  if (consecutiveEmptyResponses > 3) {
    logger.warn('Infinite loop detected, forcing stop');
    return "stop";
  }
}

Fix 4: Add Timeout Protection

Add configurable timeout for headless mode:

{
  "experimental": {
    "headless_timeout": 300000  // 5 minutes
  }
}

Environment

  • OpenCode version: big-pickle (opencode/big-pickle)
  • Affected providers: OpenCode Zen (primary issue)
  • Also affected: claude-sonnet-4.6 via github-copilot provider (similar symptoms)
  • Works fine: gpt-5.3-codex, simple prompts without tool calls
  • Process state: Alive, 0-2% CPU, consuming 500-900MB RSS
  • Key difference: Zen provider with big-pickle model causes mid-process stopping/looping

Impact

  1. Breaks automation: CLI tools that spawn opencode run as subprocess hang forever
  2. Wastes API calls: Infinite loop consumes quota/credits
  3. Blocks parent sessions: Subagent hangs block parent indefinitely
  4. Poor UX: No error message, no indication of what's wrong

References

Activity

  1. phantomwhale commented on May 15, 2026

    @phantomwhale

    Adding a clean reproduction on a different provider + model than the original report, in case it helps narrow down whether this is provider-specific or general.

    Setup

    Field Value
    opencode 1.14.47
    Provider anthropic (direct, not via Copilot)
    Model claude-opus-4-7
    Agent Custom agent, no steps cap configured
    Trigger Complex orchestration task, agent making multiple tool calls per turn

    What I observed

    30 LLM iterations in a single user turn over 8m34s, with no exiting loop ever logged. The log file only ends because I killed the process — iteration would have continued.
    From ~/.local/share/opencode/log/, filtered to service=session.prompt … loop:

    step=0   T+0s
    step=1   T+68s
    step=2   T+81s
    step=3   T+96s
    step=4   T+142s
    step=5   T+151s
    step=6   T+185s
    step=7   T+190s
    step=8   T+205s
    step=9   T+219s
    step=10  T+227s
    step=11  T+234s
    step=12  T+240s
    step=13  T+249s
    step=14  T+269s
    step=15  T+280s
    step=16  T+293s
    step=17  T+306s
    step=18  T+314s
    step=19  T+339s
    step=20  T+345s
    step=21  T+355s
    step=22  T+360s
    step=23  T+379s
    step=24  T+397s
    step=25  T+423s
    step=26  T+433s
    step=27  T+438s
    step=28  T+471s
    step=29  T+514s   ← log ends, process killed
    

    Cadence is 5–68s between iterations (mean ~17s), steady throughout — no plateau, no backoff curve.

    What's ruled out from the log

    Since past comments mentioned trouble reproducing, here's what the log shows is not happening during the loop:

    Hypothesis Verdict
    API retries (#17648 territory) 0 session.status events of type=retry
    Provider errors 0 ERROR-level lines
    Permission / question blocking 0 permission asking / question asking events
    Doom-loop detector 0 permission=doom_loop events
    Compaction / overflow 0 session.compaction events
    Subagent hang No child-session session.processor lines
    Stream stuck Each iteration emitted deltas and message.part.updated, then proceeded
    Earlier in the same session, an aborted turn showed the same pattern building up — 7 iterations in ~12 minutes before I gave up and aborted. After re-sending the prompt, the run shown above followed.

    Happy to share artifacts

    I have the full log (~100KB, 1121 lines) plus DB rows for that session. Glad to share via gist or DM if useful.

  2. Dvalin21 commented on May 16, 2026

    @Dvalin21
    Author

    Thanks for this — the cross-provider reproduction is really valuable. The 17s mean cadence with no backoff is consistent with what I saw.

    One thing that would help narrow this: in those loop iterations, did the LLM response include actual content or tool calls, or was it producing near-empty responses each time? If there's a finish_reason field in your logs for each step, that would tell us whether the session loop is ignoring finish_reason=stop (same root cause as the original report) or if the model is actually returning finish_reason=end_turn but the processor isn't recognizing it.

  3. phantomwhale commented on May 18, 2026

    @phantomwhale

    Hmmm - log file doesn't appear to have any finish_reason entries in it. I'll try and add --log-level=DEBUG to future sessions and see if I pick up more useful output

  4. gedeon1972-svg commented on Jun 3, 2026

    @gedeon1972-svg

    Hey! Dealing with those infinite retry loops on local models (like Qwen) is incredibly frustrating, especially when it just wipes the console on exit.

    I was facing the exact same architectural flaw where the agent just blindly retries instead of failing gracefully. To fix this, I built a lightweight Python action called LoopGuardian.

    It acts as a hard algorithmic brake. It audits the state and if it detects the exact same error 3 times in a row, it forces a hard stop, preventing the infinite context-bloat and saving your resources.

    You might want to drop it in your workflow to catch those loops before they freeze your terminal: https://github.com/gedeon1972-svg/loop-guardian-skill

    Hope it helps you get some control back over the agent!

  5. fllsilva commented on Jul 19, 2026

    @fllsilva

    Confirming this is still happening on 1.18.3 with OpenCode Zen/Go
    I ran into the exact same pattern described in this issue, on a clean environment:

    OpenCode version: 1.18.3
    Provider: OpenCode Zen / OpenCode Go
    Plugins: none (removed all plugins/custom agents to rule them out)
    Environment: reproduced both inside VSCode/Antigravity integrated terminal and in a native Windows CMD terminal — same behavior in both, so it's not terminal-related

    Steps to reproduce:

    Run opencode in a project folder with no AGENTS.md, no plugins, default PLAN/BUILD agents only
    Run /init to generate AGENTS.md
    Agent completes the file, then ends its turn with a text-only follow-up question ("The task is complete. What would you like to do next?")
    Instead of stopping and waiting for user input, the session immediately restarts and re-runs the same /init prompt from scratch, in an endless cycle

    This matches the root cause already identified in this thread — a text-only response with finish_reason=stop and no tool calls isn't being treated as "stop and wait for user," so the session processor calls the LLM again automatically.

    Provider is the deciding factor: the loop only happens with Zen/Go. Switching the provider to the official DeepSeek provider — same project, same /init task, nothing else changed — resolved it completely: the task completed cleanly, ended its turn, and waited for input as expected, with no restart. So this looks isolated to how Zen/Go's streaming responses are parsed, not a general OpenCode session bug.

    With every other provider I've used, this issue simply does not occur.

  6. Cody-Coleman commented on Jul 22, 2026

    @Cody-Coleman

    Can confirm this is still happening with 1.18.4, same scenario, same provider, same infinite loop.

  7. fllsilva commented on Jul 23, 2026

    @fllsilva

    It must be clear that this is not a problem with OpenCode, it is with the free models of OpenCode Zen and the paid models of OpenCode Go as well.

    Models from external providers, such as Deepseek, GLM, Kimi and others, work normally, this type of problem does not occur.

    The problem is with the models provided by OpenCode Zem and Go, as far as I can tell.

  8. uB4nshee commented on Aug 10, 2026

    @uB4nshee

    I experienced a similar infinite-loop/token-consumption issue with Claude Sonnet 4.6, although the behavior in my case appears to be slightly different from the empty-LLM-response loop described here.

    Environment

    • OpenCode: 1.18.4
    • Model: Claude Sonnet 4.6
    • RTK: 0.44.2
    • Helm: 3.21.3

    What happened

    I asked OpenCode to analyze some changes in a local Helm repository.

    During the analysis, OpenCode started repeatedly executing the same shell command:

    rtk helm plugin list
    

    The command itself was NOT hanging. It completed successfully and returned:

    NAME    VERSION    DESCRIPTION
    

    The OpenCode session nevertheless continued invoking the same operation repeatedly, resulting in an apparent tool-call loop.

    This eventually consumed my entire daily token allowance before I could stop it.

    I investigated whether RTK/Helm was causing the loop

    The RTK OpenCode plugin is installed at:

    ~/.config/opencode/plugins/rtk.ts
    

    I verified that RTK rewrites the command as expected:

    $ rtk rewrite "helm plugin list"
    rtk helm plugin list
    

    I also tested both commands independently:

    $ time helm plugin list
    NAME    VERSION    DESCRIPTION
    
    real    0m0.064s
    

    and:

    $ time rtk helm plugin list
    NAME    VERSION    DESCRIPTION
    
    real    0m0.104s
    

    Both commands complete normally.

    The RTK plugin itself only calls rtk rewrite and replaces the command when the rewritten command differs from the original. I could not find anything in the plugin that would cause the command to execute repeatedly.

    Therefore, at this point I suspect the loop is happening in the OpenCode/model tool-call cycle rather than in Helm or RTK.

    Why I'm reporting this here

    The behavior seems related to the tool/session loop described in this issue, especially because Claude Sonnet 4.6 is also involved in my case.

    However, my case differs in an important way: instead of repeated empty LLM calls after tool execution, I observed the agent repeatedly requesting the same successful tool operation.

    This also raises a broader concern about loop protection: a single model/tool interaction should not be able to repeatedly execute an identical tool call indefinitely and consume an entire token quota.

    Would this be considered the same underlying session-loop issue, or should this behavior be tracked separately?

    A configurable safeguard such as detecting repeated identical tool calls (e.g. N consecutive identical calls without progress) could also help prevent this class of issue and protect users from uncontrolled token consumption.

  9. fidodido48 commented on Aug 29, 2026

    @fidodido48

    Same started happening to me since 2 days ago.
    Big pickle model went into very verbose mode and cant seem to be able to turn it off via any opt. avail and is constantly looping same senstences over and over then stops working/goes idle unexpectedly.
    Some <invoke> and DSML cmds also appeared in the chat/block code till i put a rule for the agent to stop using these. It helped for a while to make it continue instead of stopping unexpectedly, but then it just started again after some time...

    opencode upgrade skipped: 1.18.25 is already installed

  10. Botxtrem commented on Sep 1, 2026

    @Botxtrem

    se queda en loop Voy a escribir el script correcto.

    Déjame escribir el script de inspección de match/live.

    Déjame corregir el script.

    Déjame escribir el script correcto de inspección.

    Voy a escribir el script.

    Déjame escribir el script de inspección.

    Déjame corregir el script y ejecutarlo.

    Déjame escribir el script correcto y ejecutarlo.

    Déjame escribir el script de inspección y ejecutarlo ahora.

    Voy a escribir el script correcto.

    Déjame escribir y ejecutar el script de inspección.

    Déjame corregir el script.

    Escribo el script correcto y lo ejecuto.

    Déjame escribir el script de inspección correcto.

    Déjame reescribirlo correctamente.

    Voy a reescribir el script de inspección ahora.

    Déjame corregir y ejecutar.

    Déjame escribir el script correcto.

    Escribo el script.

    Déjame escribir el script correcto de inspección y ejecutarlo de una vez.

    Déjame escribir el script.

    Escribo el script correcto.

    Déjame escribir el script de inspección correcto y ejecutarlo.

    Voy a escribirlo y ejecutarlo.

    Déjame escribir el script correcto.

    Escribo y ejecuto ahora.

    Déjame escribir el script correcto de inspección y ejecutarlo.

    Voy a hacerlo de una vez.

    Déjame escribir el script correcto y ejecutarlo.

    Escribo el script correcto y lo ejecuto ahora mismo.

    Déjame escribir el script correcto de inspección.

    Déjame escribirlo y ejecutarlo de una vez.

    Escribo y ejecuto.

    Déjame escribir el script correcto de inspección de match/live y ejecutarlo ahora.

    No más vueltas: escribo y ejecuto.

    Déjame escribir el script correcto y ejecutarlo.

  11. devsers53 commented on Sep 8, 2026

    @devsers53

    It repeats in a loop, 500 messages at a time; you can’t stop it using Esc — you can only restart the OpenCode app.

  12. thdxr commented on Oct 11, 2026

    @thdxr
    Member

    Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.

    — from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions