Repository navigation
Bash tool hangs indefinitely when PowerShell uses Start-Process with -RedirectStandardOutput/-RedirectStandardError on Windows #37838
Description
Description
Bug: Bash tool hangs indefinitely when PowerShell uses Start-Process with -RedirectStandardOutput/-RedirectStandardError on Windows
Summary
When the opencode bash tool executes a PowerShell command that combines:
Start-Processwith-RedirectStandardOutputand/or-RedirectStandardError- A long-running child process (e.g.
dotnet run,npm run dev) - Followed by synchronous commands (e.g.
; Start-Sleep ...; netstat ...)
…then the spawned child process starts successfully (e.g. web server listens on its port), but the opencode bash tool never returns. It stays in status: "running" forever, blocking the entire session.
Environment
- OS: Windows 11 (win32, x64)
- Shell: PowerShell 5.1 (via opencode
bashtool) - opencode version: installed globally via npm —
C:\Users\thailc\AppData\Roaming\npm\node_modules\opencode-ai\bin\opencode.exe - Model provider: zhipuai-coding-plan / glm-5.2
- Project: .NET 8 solution (
dotnet run --launch-profile CuDanAPI)
Reproduction
-
Start any opencode session on Windows.
-
Ask the agent (or paste yourself) the following PowerShell command via the
bashtool:Start-Process -FilePath "dotnet" -ArgumentList "run","--no-build","--launch-profile","CuDanAPI" ` -WorkingDirectory "C:\path\to\project" ` -WindowStyle Hidden ` -RedirectStandardOutput "C:\Temp\app.log" ` -RedirectStandardError "C:\Temp\app.err"; Start-Sleep -Seconds 8; netstat -ano | findstr ":5000.*LISTENING"
-
Observe:
- The child
dotnetprocess starts and binds port 5000 correctly (verifiable via Task Manager /netstatfrom another shell). - The redirect files (
app.log,app.err) are written to and contain the expected "Now listening on: http://localhost:5000" line. - But the
bashtool call never completes. The spinner keeps spinning and the prompt never comes back.
- The child
Expected behavior
The tool call should return once the synchronous part of the command (Start-Sleep + netstat) finishes — i.e. within ~8 seconds. The Start-Process-spawned child is detached and should not keep the parent pipe open.
Actual behavior
The tool is stuck in running state indefinitely. Inspecting the SQLite database at %USERPROFILE%\.local\share\opencode\opencode.db:
-- The tool part has a start time but NO end time:
SELECT json_extract(data, '$.state.status') AS status,
json_extract(data, '$.state.time.start') AS t_start,
json_extract(data, '$.state.time.end') AS t_end
FROM part
WHERE id = 'prt_f7d43da3e001ZUAb0xC1MJHgbA';
-- status = "running"
-- t_start = 1784513034969
-- t_end = NULLMeanwhile, on the OS:
> Get-Process CuDanAPI
Id ProcessName StartTime CPU
-- ----------- --------- ---
24324 CuDanAPI 7/20/2026 9:03:47 AM 0.65625
> netstat -ano | findstr ":5000.*LISTENING"
TCP 127.0.0.1:5000 0.0.0.0:0 LISTENING 24324
TCP [::1]:5000 [::]:0 LISTENING 24324
So the child is alive and well — only the tool wrapper is deadlocked.
Root cause (hypothesis)
When Start-Process is invoked with -RedirectStandardOutput / -RedirectStandardError, PowerShell opens pipe handles to capture the child's stdout/stderr. Even after Start-Process returns and the synchronous part of the command finishes, the child process still holds the inherited write end of those pipes. PowerShell's own stdout (which opencode reads from) inherits / shares the same pipe, so opencode never receives EOF and keeps waiting for more output.
This is a known class of bug on Windows (see similar reports in Node.js, Git Bash, and other shells that wrap child processes via pipes). The two contributing factors:
-WindowStyle Hiddenkeeps the child alive after the parent returns.-RedirectStandardOutput/Errorcreates a pipe whose write handle is inherited by the child.
Either factor alone is usually fine; the combination is lethal.
Workaround (already documented in our AGENTS.md)
Rule 1 — Never redirect in Start-Process when called from an opencode tool.
If you must spawn a background server:
# Tool call #1 — spawn detached, NO redirection, NO trailing synchronous commands
Start-Process -FilePath "dotnet" -ArgumentList "run","--no-build","--launch-profile","CuDanAPI" `
-WorkingDirectory "C:\path\to\project"# Tool call #2 — wait, then check (separate call)
Start-Sleep -Seconds 6
netstat -ano | findstr ":5000.*LISTENING"If logging is needed, write logs inside the child itself, or use Start-Job + Receive-Job.
Suggested fix
A few possible directions, in order of preference:
- Per-call timeout on the
bashtool. Allow the user (or agent) to specify a hard timeout after which opencode forcibly closes the pipe and marks the tool aserror: timeout. The currenttimeoutparameter only seems to apply while the command is actively writing; if the pipe is held open but silent, it never fires. - EOF detection heuristic. If a PowerShell command finishes (child process exits) but the pipe is still held by a grandchild process, opencode could detect the parent shell has exited and close the tool.
- Documentation / guardrail. At minimum, document this pattern prominently under "Windows quirks" in the official docs, and/or have the agent-facing system prompt warn against
Start-Process -RedirectStandard*+-WindowStyle Hidden. - Optional: bypass the issue by running PowerShell with
--NoProfile -Commandand explicitly closing inherited handles viaSet-Process Mitigationor similar. (More invasive.)
Impact
This is a silent, easy-to-trigger deadlock that looks like a hung session. The agent has no way to recover (it can only wait). On a long coding session this means losing all in-memory context unless the user knows to kill the opencode.exe process and opencode resume the session.
The bug has bitten us multiple times in our team workflow (we maintain a Building Management Information System on .NET 8 and routinely need to spawn dotnet run for CuDanAPI during development).
Logs / diagnostics
- opencode version: latest from npm at time of writing (July 2026)
- DB:
%USERPROFILE%\.local\share\opencode\opencode.db, tablepart, columnsdata.state.time.start/data.state.time.end - Sample tool part JSON showing the stuck state:
{
"type": "tool",
"tool": "bash",
"state": {
"status": "running",
"input": {
"command": "Start-Process -FilePath \"dotnet\" ... -RedirectStandardOutput \"...\" -RedirectStandardError \"...\"; Start-Sleep -Seconds 8; netstat -ano | findstr \":5000.*LISTENING\"",
"workdir": "C:\\Users\\...\\BMIS"
},
"time": { "start": 1784513034969 },
"metadata": { "output": "TCP 127.0.0.1:5000 ... LISTENING 24324" }
}
}Note: the metadata.output already contains the expected netstat result — proving the synchronous part of the command ran — yet status is still "running" and time.end is missing.
Related
- Similar reports in other tools: Node.js
child_process.spawnwith inherited stdio on Windows; VS Code's integrated terminal; Git Bash's handling ofstart /B.
Happy to provide more diagnostics or test a fix if needed. Thanks for the great tool! 🙏
Plugins
No response
OpenCode version
No response
Steps to reproduce
No response
Screenshot and/or share link
No response
Operating System
No response
Terminal
No response
Activity
github-actions commentedon Jul 20, 2026
This issue might be a duplicate of existing issues. Please check:
Start-Process -RedirectStandardOutput/Error+ long-lived child scenario on Windows 11 + PowerShell 5.1runningafter the process exits)Yhx888 commentedon Aug 4, 2026
Additional reproducible data point (Windows 11, PowerShell 5.1, opencode-ai 1.18.11 via npm)
Same root cause reproduced twice in one session, with exact timestamps from
opencode.db:Start-Process node -ArgumentList "server.js" -WorkingDirectory "C:\HOME\Project\Pi desktop" -WindowStyle Hidden -RedirectStandardOutput "...\server.log" -RedirectStandardError "...\server.err.log"started a long-lived Node HTTP server (never exits). The bash tool call stayedrunningfor 9h47m (23:49:50 → 09:37:24 local, start=1785772190318 end=1785807443724) until manually aborted (Tool execution aborted,interrupted: true).Stop-Process+Start-Process ... -RedirectStandardOutput/-RedirectStandardError). It is stuck inrunningagain with no end timestamp. The server process itself started fine (port 8321 listening, log file written).DB evidence (
parttable in%USERPROFILE%\.local\share\opencode\opencode.db): tool part hasstate.status: "running",time.startset,time.endmissing — exactly the pattern described in #32504 / #24784 (long-lived child keeps the redirect handles / pipe open, tool waits for pipe EOF instead of the synchronous command completing).Impact: the entire session is blocked for hours; the only escape is a manual interrupt, after which the agent works normally again.
Expectation: the tool call should return once the synchronous part (
Start-Process+Start-Sleep+Get-Content) finishes; a detached long-lived child should not keep the tool open.