git bash.exe processes on Windows get orphaned #3537

Open
opened 2026-02-16 17:40:34 -05:00 by yindo · 8 comments
Owner

Originally created by @nikitakot on GitHub (Dec 14, 2025).

Originally assigned to: @rekram1-node on GitHub.

Description

Processes which are launched on Windows via Git bash.exe get orphaned.

OpenCode version

1.0.152

Steps to reproduce

  1. start opencode
  2. ask it to run npx serve
  3. interrupt the execution
  4. the bash.exe will still be running and will still be occupying the default serve 3000 localhost port

Screenshot and/or share link

Image

Operating System

Windows 11, pwsh, wezterm

Terminal

wezterm

Originally created by @nikitakot on GitHub (Dec 14, 2025). Originally assigned to: @rekram1-node on GitHub. ### Description Processes which are launched on Windows via Git bash.exe get orphaned. ### OpenCode version 1.0.152 ### Steps to reproduce 1. start `opencode` 2. ask it to run `npx serve` 3. interrupt the execution 4. the `bash.exe` will still be running and will still be occupying the default `serve` 3000 localhost port ### Screenshot and/or share link <img width="1489" height="960" alt="Image" src="https://github.com/user-attachments/assets/785b865c-f995-4b71-a6f2-4fa7d267169a" /> ### Operating System Windows 11, pwsh, wezterm ### Terminal wezterm
yindo added the bug label 2026-02-16 17:40:34 -05:00
Author
Owner

@github-actions[bot] commented on GitHub (Dec 14, 2025):

This issue might be a duplicate of existing issues. Please check:

  • #4384: Process sometimes doesn't close after response is finished (Windows-specific, involves process cleanup issues)
  • #3763: [Windows] interactive call breaking tui (Windows Bash-related process issues)

Feel free to ignore if none of these address your specific case.

@github-actions[bot] commented on GitHub (Dec 14, 2025): This issue might be a duplicate of existing issues. Please check: - #4384: Process sometimes doesn't close after response is finished (Windows-specific, involves process cleanup issues) - #3763: [Windows] interactive call breaking tui (Windows Bash-related process issues) Feel free to ignore if none of these address your specific case.
Author
Owner

@anntnzrb commented on GitHub (Dec 20, 2025):

Root cause analysis

This is the same underlying issue as #3057 but Windows-specific. The process cleanup code has inconsistent implementations:

Shell cleanup (shell/shell.ts) — Robust

process.kill(-pid, "SIGTERM")      // Kill process group
await Bun.sleep(200)
if (!opts?.exited?.()) {
  process.kill(-pid, "SIGKILL")    // Escalate if still alive
}

LSP cleanup (lsp/client.ts) — Weak

input.server.process.kill()        // Just SIGTERM, no group, no escalation

Why orphans happen

  1. No process group kill (-pid) — Child processes spawned by the parent (e.g., tsserver from typescript-language-server, or subshells from bash.exe) are not targeted
  2. No SIGKILL escalation — If SIGTERM is ignored, the process survives
  3. No exit confirmation — Code assumes .kill() works but never verifies

Windows-specific note

On Windows, the shell module has a fallback path using taskkill:

if (process.platform === "win32") {
  const killer = spawn("taskkill", ["/pid", String(pid), "/f", "/t"])
  // ...
}

The /t flag kills the process tree. This pattern should also be applied to LSP cleanup on Windows.

Related: #3057 (general process cleanup discussion with suggested fix)

@anntnzrb commented on GitHub (Dec 20, 2025): ## Root cause analysis This is the same underlying issue as #3057 but Windows-specific. The process cleanup code has inconsistent implementations: ### Shell cleanup (`shell/shell.ts`) — Robust ```typescript process.kill(-pid, "SIGTERM") // Kill process group await Bun.sleep(200) if (!opts?.exited?.()) { process.kill(-pid, "SIGKILL") // Escalate if still alive } ``` ### LSP cleanup (`lsp/client.ts`) — Weak ```typescript input.server.process.kill() // Just SIGTERM, no group, no escalation ``` ### Why orphans happen 1. **No process group kill (`-pid`)** — Child processes spawned by the parent (e.g., `tsserver` from `typescript-language-server`, or subshells from `bash.exe`) are not targeted 2. **No SIGKILL escalation** — If SIGTERM is ignored, the process survives 3. **No exit confirmation** — Code assumes `.kill()` works but never verifies ### Windows-specific note On Windows, the shell module has a fallback path using `taskkill`: ```typescript if (process.platform === "win32") { const killer = spawn("taskkill", ["/pid", String(pid), "/f", "/t"]) // ... } ``` The `/t` flag kills the process tree. This pattern should also be applied to LSP cleanup on Windows. Related: #3057 (general process cleanup discussion with suggested fix)
Author
Owner

@anntnzrb commented on GitHub (Dec 20, 2025):

Opened PRs to address this:

  • #5875 - Fix LSP process cleanup (uses Shell.killTree() + detached: true)
  • #5876 - Fix PTY process cleanup (adds killPtyTree() with taskkill /t on Windows)

Both now properly kill process trees on Windows using taskkill /pid <pid> /f /t.

@anntnzrb commented on GitHub (Dec 20, 2025): Opened PRs to address this: - #5875 - Fix LSP process cleanup (uses `Shell.killTree()` + `detached: true`) - #5876 - Fix PTY process cleanup (adds `killPtyTree()` with `taskkill /t` on Windows) Both now properly kill process trees on Windows using `taskkill /pid <pid> /f /t`.
Author
Owner

@rekram1-node commented on GitHub (Dec 20, 2025):

@anntnzrb I dont think the lsps are having an orphan issue? I think it’s just the bash tool

@rekram1-node commented on GitHub (Dec 20, 2025): @anntnzrb I dont think the lsps are having an orphan issue? I think it’s just the bash tool
Author
Owner

@anntnzrb commented on GitHub (Dec 20, 2025):

@rekram1-node You're right that the user-reported symptom (scripts running after Ctrl+C) is specifically about the bash tool - that's a separate abort race condition where void kill() is fire-and-forget.

However, the LSP fix addresses a different scenario I encountered: when using git worktrees with parallel subagents, each worktree spawns its own set of LSPs. After the session ends, orphaned tsserver processes remained because:

  1. LSP servers weren't spawned with detached: true (needed for process group kill)
  2. shutdown() used plain process.kill() instead of Shell.killTree()

So the LSP PR (#5875) addresses LSP cleanup on session disposal, while the bash issue (#3057) is about abort during execution. They're related but different code paths.

Happy to close the LSP PR if you don't think it's needed though!

@anntnzrb commented on GitHub (Dec 20, 2025): @rekram1-node You're right that the user-reported symptom (scripts running after Ctrl+C) is specifically about the bash tool - that's a separate abort race condition where `void kill()` is fire-and-forget. However, the LSP fix addresses a different scenario I encountered: when using **git worktrees with parallel subagents**, each worktree spawns its own set of LSPs. After the session ends, orphaned `tsserver` processes remained because: 1. LSP servers weren't spawned with `detached: true` (needed for process group kill) 2. `shutdown()` used plain `process.kill()` instead of `Shell.killTree()` So the LSP PR (#5875) addresses LSP cleanup on **session disposal**, while the bash issue (#3057) is about **abort during execution**. They're related but different code paths. Happy to close the LSP PR if you don't think it's needed though!
Author
Owner

@rekram1-node commented on GitHub (Dec 20, 2025):

maybe im misunderstanding but Because we dont spawn lsps with detached true i dont think we need special kill logic

or am i misunderstanding u?

Ur diagnosis seemed ai generated so im trying to make sure we are both actually understanding source of issue instead of relying on its response

@rekram1-node commented on GitHub (Dec 20, 2025): maybe im misunderstanding but Because we dont spawn lsps with detached true i dont think we need special kill logic or am i misunderstanding u? Ur diagnosis seemed ai generated so im trying to make sure we are both actually understanding source of issue instead of relying on its response
Author
Owner

@anntnzrb commented on GitHub (Dec 20, 2025):

Fair point - let me clarify with a concrete example of what I observed:

  1. I was running opencode in multiple git worktrees simultaneously (parallel subagents)
  2. Each worktree instance spawned typescript-language-server which spawns tsserver as a child
  3. After closing opencode, I ran ps aux | grep tsserver and saw 7+ orphaned tsserver processes still running

The current shutdown() calls process.kill() which sends SIGTERM to typescript-language-server, but its child tsserver survives because:

  • Without detached: true, the child isn't in a killable process group
  • Without Shell.killTree(), there's no SIGKILL escalation if SIGTERM is ignored

My PR adds both:

  1. detached: true when spawning → makes the process a group leader
  2. Shell.killTree() on shutdown → kills the group with -pid and escalates to SIGKILL

You're right that without detached: true, the group kill won't work - that's why the PR adds both together.

@anntnzrb commented on GitHub (Dec 20, 2025): Fair point - let me clarify with a concrete example of what I observed: 1. I was running opencode in multiple git worktrees simultaneously (parallel subagents) 2. Each worktree instance spawned `typescript-language-server` which spawns `tsserver` as a child 3. After closing opencode, I ran `ps aux | grep tsserver` and saw 7+ orphaned `tsserver` processes still running The current `shutdown()` calls `process.kill()` which sends SIGTERM to `typescript-language-server`, but its child `tsserver` survives because: - Without `detached: true`, the child isn't in a killable process group - Without `Shell.killTree()`, there's no SIGKILL escalation if SIGTERM is ignored My PR adds both: 1. `detached: true` when spawning → makes the process a group leader 2. `Shell.killTree()` on shutdown → kills the group with `-pid` and escalates to SIGKILL You're right that without `detached: true`, the group kill won't work - that's why the PR adds both together.
Author
Owner

@rekram1-node commented on GitHub (Dec 20, 2025):

Okay cool, yeah these days if I read a big AI diagnosis I really need to be careful because most of the time it's slop, you're response here sounds a lot more sane hahaa so thank you!

I'll take a look at those

@rekram1-node commented on GitHub (Dec 20, 2025): Okay cool, yeah these days if I read a big AI diagnosis I really need to be careful because most of the time it's slop, you're response here sounds a lot more sane hahaa so thank you! I'll take a look at those
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#3537