anomalyco / anomalyco/opencode

bash tool: timeout kills the shell but does not abandon the stdio read — call duration is unbounded

Open
#49,169 1 comment 0 reactions 1 assignee View on GitHub

@neriousy is already working on this.

Since Sep 15, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Description
Summary

The bash tool's timeout terminates the shell process on schedule, but then keeps reading its
stdout/stderr pipe. If the command spawned a process that inherited those handles, EOF never arrives
while that process lives — so the call blocks well past the timeout, unbounded for a long-lived server.

The returned message is also misleading: it says the command was terminated at the timeout, when in fact
substantially more wall-clock time elapsed before the call returned.

  • Bounded, reproducible: timeout: 5000 → call returned after 25.79 s
  • Observed in practice: timeout: 120000 → call returned after 1842.5 s (30.7 min), and only
    because the process tree was killed externally
Suggested fix

On timeout, stop awaiting stream EOF: resolve from process exit and drain stdio with a deadline, so
timeout is a hard upper bound on the call regardless of stream state. Optionally also kill the
process group / job object so descendants don't survive the timeout — they currently do, and are
never reaped.

Plugins

OMO

OpenCode version

1.18.30

Steps to reproduce

Part A — the mechanism (standalone, no opencode needed, ~25 s)

Paste into PowerShell. It appears to freeze for ~25 s; that freeze is the bug, and it self-terminates.

$inner = Join-Path $env:TEMP 'pipedemo-inner.ps1'
@'
Start-Process -FilePath "powershell.exe" `
  -ArgumentList @("-NoProfile", "-Command", "Start-Sleep -Seconds 25") `
  -WindowStyle Hidden `
  -RedirectStandardOutput "$env:TEMP\hang-o.log" `
  -RedirectStandardError "$env:TEMP\hang-e.log" | Out-Null
"INNER_SCRIPT_DONE at $(Get-Date -Format o)"
'@ | Set-Content -LiteralPath $inner -Encoding utf8

$sw  = [System.Diagnostics.Stopwatch]::StartNew()
$psi = [System.Diagnostics.ProcessStartInfo]::new()
$psi.FileName  = 'powershell.exe'
$psi.Arguments = "-NoProfile -File `"$inner`""
$psi.RedirectStandardOutput = $true
$psi.UseShellExecute = $false
$p = [System.Diagnostics.Process]::Start($psi)
$null = $p.WaitForExit(60000); $exitedAt = $sw.Elapsed.TotalSeconds
$out  = $p.StandardOutput.ReadToEnd(); $eofAt = $sw.Elapsed.TotalSeconds
"child process EXITED: $([math]::Round($exitedAt,2)) s"
"child stdout EOF    : $([math]::Round($eofAt,2)) s"
Remove-Item -LiteralPath $inner -Force -ErrorAction SilentlyContinue

Measured (Windows 11 build 26200, PowerShell 7.6.6):

child process EXITED: 0.26 s
child stdout EOF    : 25.41 s

25.15 s of blocking after the child had already exited. Running the inner script directly in a
console returns in 0.58 s — the hang only appears when stdout is a pipe being read to EOF, which is
why it never reproduces interactively.

Part B — the timeout does not bound the call (through the bash tool, ~25 s)

Invoke the bash tool with timeout: 5000 and this command:

$inner = Join-Path $env:TEMP 'to-test.ps1'
@'
Start-Process -FilePath "powershell.exe" -ArgumentList @("-NoProfile","-Command","Start-Sleep -Seconds 25") -WindowStyle Hidden -RedirectStandardOutput "$env:TEMP\to-o.log" -RedirectStandardError "$env:TEMP\to-e.log" | Out-Null
"CHILD_SPAWNED"
'@ | Set-Content -LiteralPath $inner -Encoding utf8
& powershell.exe -NoProfile -File $inner
"script finished at $(Get-Date -Format o)"

Expected: the call returns at ~5 s.
Actual: it returns at 25.79 s (recorded state.time.end - state.time.start), i.e. when the
detached child exits and EOF finally arrives — while reporting:

shell tool terminated command after exceeding timeout 5000 ms

The timeout did fire and did kill the shell; it simply did not stop the read. With a server in place
of Start-Sleep 25, the call never returns at all.

Screenshot and/or share link

No response

Operating System

Windows 11 Pro build 26200, AMD64

Terminal

PowerShell 7.6.6

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.