You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
opencode run hangs after completing tool calls — process never exits #17516
opencode run hangs indefinitely after completing all tool calls. The model finishes its work (reads a file, writes a file), but the process never exits. It stays alive with no output, requiring a manual kill.
Reproduction
# Create a simple prompt that asks the model to edit a fileecho"Read compress.py and rewrite it with a better algorithm. Edit the file and exit." \
| opencode run -m github-copilot/claude-sonnet-4.6
The agent:
Reads the file (tool call completes, output shown)
Writes the file (tool call completes, "Wrote file successfully." shown)
Hangs indefinitely — no more output, process stays alive
Observed behavior
> build · claude-sonnet-4.6
→ Read compress.py
← Write compress.py
Wrote file successfully.
# <--- hangs here forever, no exit
Expected behavior
After the model finishes all tool calls and produces its final response, opencode run should exit with code 0.
Context
This was discovered while using opencode run as the agent command in a CLI optimization loop (ratchet). The loop spawns opencode run via sh -c with stdin redirection (< prompt.md), monitors try_wait() on the child process, and expects it to exit after the model completes. The process never terminates, causing the loop to freeze.
Simple prompts (e.g., "What is 2+2?") exit fine. The hang occurs specifically when the model uses tools (Read/Write) and then has nothing left to do.
Environment
opencode version: 1.2.26
macOS (darwin)
Model: github-copilot/claude-sonnet-4.6
Invocation: opencode run -m github-copilot/claude-sonnet-4.6 < prompt.md
The LLM is being called repeatedly but produces no tool calls. The session processor doesn't detect this as "done" and keeps looping. There are no errors, no timeouts — just an infinite cycle of empty LLM calls.
How to reproduce
# Create a simple prompt that asks an agent to edit a file
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. Exit immediately after editing.EOF
opencode run --print-logs --log-level DEBUG \
-m github-copilot/claude-sonnet-4.6 < /tmp/test-prompt.md 2>debug.log
# Process hangs after Write completes# debug.log shows session.prompt stepping from 2 to 42+ with no tool calls
Hypothesis
After the model's final text response (post-tool-call), opencode's session loop should detect that the model has finished (e.g., finish_reason=stop or no tool calls in a text-only response) and exit. Instead, it treats the response as requiring another iteration and calls the LLM again, creating an infinite loop.
Update: Confirmed this is specific to github-copilot/claude-sonnet-4.6. Switching to gpt-5.3-codex via the same opencode run command works perfectly — the process exits cleanly after tool calls complete.
The issue appears to be that the Sonnet model (via the GitHub Copilot provider) doesn't produce a proper stop/finish signal after its final text response, causing the session loop to keep iterating.
We run a discovery pipeline that uses opencode run in headless mode to launch a master orchestrator, which then dispatches ~15 sequential subagent Tasks via the Task tool. Each Task creates a child session that reads files, analyzes code, and writes draft reports.
# Master session (Sonnet 4.6) dispatches workers (GPT-5.4 per agent definition)
opencode run -m github-copilot/claude-sonnet-4.6 \
"Read master-orchestrator.md and follow its instructions..."
Observations across 3 independent batch runs tonight (8, 12, and 4 sessions)
Run
Sessions
Model (master/worker)
Agents completed
Drafts written
Outcome
1
8
Sonnet 4.6 / GPT-5.4
2 of 120
0
All stalled
2
12
Sonnet 4.6 / Sonnet 4.6
0 of 180
0
All stalled
3
4
Sonnet 4.6 / GPT-5.4
0 of 60
0
All stalled
One leftover GPT-5.4 session from run 1 did eventually produce 1 draft file before being killed, suggesting the bug is non-deterministic.
Log evidence
The OpenCode internal log (~/.local/share/opencode/log/) confirms @alephmelo's finding:
Log output stops completely at the moment the first subagent Task is dispatched
Log file shows 581 lines, all timestamped within the first 23 seconds — then absolute silence for 14+ minutes
Processes remain alive (confirmed via ps), consuming 500-900 MB RSS each, but at 0-2% CPU
No ERROR or WARN entries in the log — the hang is completely silent
Additional data points
This pipeline produced 900+ drafts successfully before March 10 using the same agent definitions and headless invocation pattern
The opencode binary was updated to v1.2.26 on March 13
github-copilot/gpt-5.4 workers are also affected (not just Sonnet)
The permission=task pattern=discovery-worker evaluation shows action: allow — permissions are not the blocker
Impact
The infinite agentic loop in headless mode breaks any automation that uses Task/subagent dispatch, since the parent session blocks on <-done (agent-tool.go:128) waiting for the child session to return. If the child enters the empty-loop state, the parent hangs forever, cascading upward.
In every case, the write is completed, but either the tool call doesn't return or the agent doesn't resume. This occurs even if I make it utilize a subagent to complete write tasks. It looks to me that it's only edit tool and write tool that have the issue? Another thing I noticed is that if it batches up 10 edits it will go through all 10 but after the last one it never returns whether it is the main agent or a sub agent. Does it matter if it's writing to a symlinked directory? My project dir is symlinked. I can kind of workaround by telling it to make the edits with bash, but I'm sure this will have some side effect. Opus 4.6
Started with: opencode --print-logs --log-level DEBUG
Reproduction
Two sequential Grep tool calls in the same session. Both search a ~3,700-line Python file. The first succeeds; the second hangs for 4+ minutes until the user aborts.
4 message.part.updated events (tool result streaming back), then the session advances to step 1.
Hanging call (12:53:30):
12:53:30 +519ms service=permission permission=grep pattern=def.*batch|batch_fix|_batch ... evaluated → allowed
12:53:30 +2ms service=bus type=message.part.updated publishing
12:53:30 +8ms service=bus type=message.part.updated publishing
← 261,986ms of ABSOLUTE SILENCE — no log entries at all
12:57:52 +261986ms service=server method=POST path=/session/<id>/abort request ← user aborted after 4m22s
12:57:52 +3ms ERROR service=session.processor error=Aborted process
Only 2 message.part.updated events, then nothing. No error, no timeout, no crash. The grep tool entered a black hole — permission was granted, but the tool execution produced zero further log entries and never completed.
Key observations
No grep execution logs exist — there are no service=grep or service=tool.grep or rg-related log lines anywhere in the file. The grep tool's internal execution (spawning ripgrep, reading stdout, formatting results) is completely uninstrumented at both INFO and DEBUG levels.
--log-level DEBUG produces zero DEBUG lines — despite passing --log-level DEBUG, the entire log file contains only INFO and ERROR entries. Either the flag isn't being respected, or no component emits DEBUG-level logs.
The hang is between permission grant and tool result return — the permission system evaluates and allows the call, but the tool never produces a result. The session processor blocks waiting for it.
Pattern may matter — the successful call used a simple literal pattern (def _run_batch_fix), while the hanging call used alternation (def.*batch|batch_fix|_batch). This could be coincidental, but it might point to a ripgrep stdout pipe issue with certain output patterns.
This also affects the Edit and Write tools (same session, confirmed by user over multiple prior sessions). The symptom is identical: the edit is applied to disk successfully, but the tool call never returns.
This seems like a generic tool execution pipeline issue rather than grep-specific. Something in the tool → result → session processor chain silently drops the completion signal under certain conditions.
Please see my above message, Im unable to reproduce plz provide reproduction steps or share a session that hung.
You can send me your session using this: opencode export > session.json
Also be careful saying it "hung" sometimes inference takes a bit, or sometimes the tool call is taking a while. It would be good to provide the session so I can see what "hung"
I mean I'm tailing the log and it also stops doing anything for 10 minutes to 10 hours. So it's def hung. I can't seem to reproduce it every time, but now I have debug logging on. A lot of my projects switched to doing bash edits so I stopped seeing it.
@MattPark are you using any plugins? What provider are u seeing this with? Just trying to figure out what u do... We use opencode run a lot internally and none of us have managed to replicate
exitCode resolves only when close fires. In Node.js, close fires after stdio streams are closed, not merely after the process exits.
For Gradle/JVM process trees on Windows, child processes or Gradle daemons can inherit stdout/stderr pipe handles. Then the main Gradle process exits, but one inherited pipe handle stays open. Result: exit fires, close does not, and OpenCode still thinks the command is running.
A possible fix would be to resolve process termination from the exit event, and treat close only as stream/output completion.
Conceptually:
proc.on("exit",(...args)=>{if(end)returnend=trueDeferred.doneUnsafe(signal,Exit.succeed(args))})proc.on("close",(...args)=>{// optional: use this for stream drain / output completion,// but not as the only exit signal})
Or keep separate deferreds:
one deferred for process exit code, resolved by exit
one deferred for stdio completion, resolved by close
Then shell command completion should race/wait on the exit deferred, not the close deferred. Output collection can still continue until close or until a short drain timeout.
As a workaround outside OpenCode, I had success with a Gradle wrapper script that redirects Gradle stdout/stderr to temp files and tails those files back to OpenCode. That prevents Gradle/JVM children from inheriting OpenCode's pipe handles directly. This avoids the hanging close event in practice, but the underlying issue seems to be that process lifetime and stdio lifetime are currently coupled.
@rekram1-node If you are still looking for reproduction of this issue, I had reproduced the issue in test cases before trying to fix it in a PR, but it seems like other PRs exist that already fix it.
Activity
github-actions commentedon Mar 14, 2026
This issue might be a duplicate of existing issues. Please check:
opencode runhangs when model attempts to use question tool — same command, same hang behavior in non-interactive modeopencode runin automation contexts — similar use case (spawning opencode from an external automation loop)alephmelo commentedon Mar 14, 2026
Debug logs (
--print-logs --log-level DEBUG)Reproduced consistently. The root cause is visible in the logs:
opencode enters an infinite agentic loop after the model finishes its tool calls.
Timeline
session.prompt step=2→ resolveTools →session.processor process→llm stream— no tool call, no visible outputPattern per step (steps 2-42)
Every step follows the exact same sequence:
The LLM is being called repeatedly but produces no tool calls. The session processor doesn't detect this as "done" and keeps looping. There are no errors, no timeouts — just an infinite cycle of empty LLM calls.
How to reproduce
Hypothesis
After the model's final text response (post-tool-call), opencode's session loop should detect that the model has finished (e.g.,
finish_reason=stopor no tool calls in a text-only response) and exit. Instead, it treats the response as requiring another iteration and calls the LLM again, creating an infinite loop.alephmelo commentedon Mar 14, 2026
Update: Confirmed this is specific to
github-copilot/claude-sonnet-4.6. Switching togpt-5.3-codexvia the sameopencode runcommand works perfectly — the process exits cleanly after tool calls complete.The issue appears to be that the Sonnet model (via the GitHub Copilot provider) doesn't produce a proper stop/finish signal after its final text response, causing the session loop to keep iterating.
coygeek commentedon Mar 15, 2026
Confirming — also affects Task tool (subagent dispatch), not just direct
opencode runEnvironment: opencode 1.2.26, macOS (darwin), GitHub Copilot provider
Reproduction scenario
We run a discovery pipeline that uses
opencode runin headless mode to launch a master orchestrator, which then dispatches ~15 sequential subagent Tasks via the Task tool. Each Task creates a child session that reads files, analyzes code, and writes draft reports.Observations across 3 independent batch runs tonight (8, 12, and 4 sessions)
One leftover GPT-5.4 session from run 1 did eventually produce 1 draft file before being killed, suggesting the bug is non-deterministic.
Log evidence
The OpenCode internal log (
~/.local/share/opencode/log/) confirms @alephmelo's finding:ps), consuming 500-900 MB RSS each, but at 0-2% CPUAdditional data points
github-copilot/gpt-5.4workers are also affected (not just Sonnet)permission=task pattern=discovery-workerevaluation showsaction: allow— permissions are not the blockerImpact
The infinite agentic loop in headless mode breaks any automation that uses Task/subagent dispatch, since the parent session blocks on
<-done(agent-tool.go:128) waiting for the child session to return. If the child enters the empty-loop state, the parent hangs forever, cascading upward.micartey commentedon Mar 27, 2026
I have the same issue... Please take a look at it as the current state of the cli is not usable because of this
MattPark commentedon Apr 1, 2026
In every case, the write is completed, but either the tool call doesn't return or the agent doesn't resume. This occurs even if I make it utilize a subagent to complete write tasks. It looks to me that it's only edit tool and write tool that have the issue? Another thing I noticed is that if it batches up 10 edits it will go through all 10 but after the last one it never returns whether it is the main agent or a sub agent. Does it matter if it's writing to a symlinked directory? My project dir is symlinked. I can kind of workaround by telling it to make the edits with bash, but I'm sure this will have some side effect. Opus 4.6
rekram1-node commentedon Apr 7, 2026
Hm I cannot replicate can someone provide exact steps, the provider and model thtey are using?
ideally something like:
opencode run "<some prompt>" --model <providerID>/<modelID> --print-logsWould recommedn using print logs so that u can show what it hangs on
maxiedaniels commentedon Apr 10, 2026
Plagued by this same issue. Driving me nuts!! I'm exclusively using OpenAI + GPT 5.4.
MattPark commentedon Apr 12, 2026
Reproduction with
--print-logs— Grep tool hangEnvironment:
github-copilot/claude-opus-4.6opencode --print-logs --log-level DEBUGReproduction
Two sequential Grep tool calls in the same session. Both search a ~3,700-line Python file. The first succeeds; the second hangs for 4+ minutes until the user aborts.
Call 1 (succeeds):
Grep(pattern="def _run_batch_fix", include="audit.py", path="<project>/core/plugins")Call 2 (hangs):
Grep(pattern="def.*batch|batch_fix|_batch", include="audit.py")The only difference is call 2 uses a broader regex with alternation (
|) and no explicitpath.Log evidence
Successful call (12:53:23):
4
message.part.updatedevents (tool result streaming back), then the session advances to step 1.Hanging call (12:53:30):
Only 2
message.part.updatedevents, then nothing. No error, no timeout, no crash. The grep tool entered a black hole — permission was granted, but the tool execution produced zero further log entries and never completed.Key observations
No grep execution logs exist — there are no
service=greporservice=tool.grepor rg-related log lines anywhere in the file. The grep tool's internal execution (spawning ripgrep, reading stdout, formatting results) is completely uninstrumented at both INFO and DEBUG levels.--log-level DEBUGproduces zero DEBUG lines — despite passing--log-level DEBUG, the entire log file contains only INFO and ERROR entries. Either the flag isn't being respected, or no component emits DEBUG-level logs.The hang is between permission grant and tool result return — the permission system evaluates and allows the call, but the tool never produces a result. The session processor blocks waiting for it.
Pattern may matter — the successful call used a simple literal pattern (
def _run_batch_fix), while the hanging call used alternation (def.*batch|batch_fix|_batch). This could be coincidental, but it might point to a ripgrep stdout pipe issue with certain output patterns.This also affects the Edit and Write tools (same session, confirmed by user over multiple prior sessions). The symptom is identical: the edit is applied to disk successfully, but the tool call never returns.
This seems like a generic tool execution pipeline issue rather than grep-specific. Something in the tool → result → session processor chain silently drops the completion signal under certain conditions.
rekram1-node commentedon Apr 13, 2026
Please see my above message, Im unable to reproduce plz provide reproduction steps or share a session that hung.
You can send me your session using this:
opencode export > session.jsonAlso be careful saying it "hung" sometimes inference takes a bit, or sometimes the tool call is taking a while. It would be good to provide the session so I can see what "hung"
MattPark commentedon Apr 13, 2026
I mean I'm tailing the log and it also stops doing anything for 10 minutes to 10 hours. So it's def hung. I can't seem to reproduce it every time, but now I have debug logging on. A lot of my projects switched to doing bash edits so I stopped seeing it.
rekram1-node commentedon Apr 16, 2026
@MattPark are you using any plugins? What provider are u seeing this with? Just trying to figure out what u do... We use opencode run a lot internally and none of us have managed to replicate
MattPark commentedon Apr 21, 2026
@rekram1-node

In interactive mode: session.json
Ok this is after it hangs for 5 minutes:
The subagent: session2.json

In run mode I actually got an error: session3.json
Ran it again with dangerously-skip-permissions and it stalled again: session4.json
MattPark commentedon Apr 28, 2026
@rekram1-node did this debug info help or do you need something else?
fix: detect opencode completion via JSON event stream, not heuristics
moritzfl commentedon May 20, 2026
I think this may be caused by OpenCode waiting for Node's
closeevent as the authoritative process-termination signal.Relevant code:
packages/core/src/cross-spawn-spawner.ts:274-280packages/opencode/src/tool/shell.ts:542-546Current behavior:
exitCoderesolves only whenclosefires. In Node.js,closefires after stdio streams are closed, not merely after the process exits.For Gradle/JVM process trees on Windows, child processes or Gradle daemons can inherit stdout/stderr pipe handles. Then the main Gradle process exits, but one inherited pipe handle stays open. Result:
exitfires,closedoes not, and OpenCode still thinks the command is running.A possible fix would be to resolve process termination from the
exitevent, and treatcloseonly as stream/output completion.Conceptually:
Or keep separate deferreds:
exitcloseThen shell command completion should race/wait on the exit deferred, not the close deferred. Output collection can still continue until
closeor until a short drain timeout.As a workaround outside OpenCode, I had success with a Gradle wrapper script that redirects Gradle stdout/stderr to temp files and tails those files back to OpenCode. That prevents Gradle/JVM children from inheriting OpenCode's pipe handles directly. This avoids the hanging
closeevent in practice, but the underlying issue seems to be that process lifetime and stdio lifetime are currently coupled.moritzfl commentedon May 20, 2026
For reproduction without relying on a specific tool (I tested this with a powershell script on windows):
Expected: OpenCode returns only after C exits.
Expected: OpenCode returns immediately.
Current behavior:
Needed behavior:
moritzfl commentedon Jun 8, 2026
@rekram1-node If you are still looking for reproduction of this issue, I had reproduced the issue in test cases before trying to fix it in a PR, but it seems like other PRs exist that already fix it.
8327a22
docs: update compatibility table with OpenCode limitations
Bound opencode's non-exiting provider inside the container
github-actions commentedon Aug 8, 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.