Skip to content

Bash tool hangs indefinitely when PowerShell uses Start-Process with -RedirectStandardOutput/-RedirectStandardError on Windows #37838

Description

@thaimeobk

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-Process with -RedirectStandardOutput and/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 bash tool)
  • 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

  1. Start any opencode session on Windows.

  2. Ask the agent (or paste yourself) the following PowerShell command via the bash tool:

    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"
  3. Observe:

    • The child dotnet process starts and binds port 5000 correctly (verifiable via Task Manager / netstat from 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 bash tool call never completes. The spinner keeps spinning and the prompt never comes back.

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   = NULL

Meanwhile, 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:

  1. -WindowStyle Hidden keeps the child alive after the parent returns.
  2. -RedirectStandardOutput/Error creates 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:

  1. Per-call timeout on the bash tool. Allow the user (or agent) to specify a hard timeout after which opencode forcibly closes the pipe and marks the tool as error: timeout. The current timeout parameter only seems to apply while the command is actively writing; if the pipe is held open but silent, it never fires.
  2. 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.
  3. 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.
  4. Optional: bypass the issue by running PowerShell with --NoProfile -Command and explicitly closing inherited handles via Set-Process Mitigation or 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, table part, columns data.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.spawn with inherited stdio on Windows; VS Code's integrated terminal; Git Bash's handling of start /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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions