git bash.exe processes on Windows get orphaned
Description
Processes which are launched on Windows via Git bash.exe get orphaned.
OpenCode version
1.0.152
Steps to reproduce
- start
opencode - ask it to run
npx serve - interrupt the execution
- the
bash.exewill still be running and will still be occupying the defaultserve3000 localhost port
Screenshot and/or share link
Operating System
Windows 11, pwsh, wezterm
Terminal
wezterm
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.
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
-
No process group kill (
-pid) — Child processes spawned by the parent (e.g.,tsserverfromtypescript-language-server, or subshells frombash.exe) are not targeted - No SIGKILL escalation — If SIGTERM is ignored, the process survives
-
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)
Opened PRs to address this:
- #5875 - Fix LSP process cleanup (uses
Shell.killTree()+detached: true) - #5876 - Fix PTY process cleanup (adds
killPtyTree()withtaskkill /ton Windows)
Both now properly kill process trees on Windows using taskkill /pid <pid> /f /t.
@anntnzrb I dont think the lsps are having an orphan issue? I think it’s just the bash tool
@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:
- LSP servers weren't spawned with
detached: true(needed for process group kill) -
shutdown()used plainprocess.kill()instead ofShell.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!
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
Fair point - let me clarify with a concrete example of what I observed:
- I was running opencode in multiple git worktrees simultaneously (parallel subagents)
- Each worktree instance spawned
typescript-language-serverwhich spawnstsserveras a child - After closing opencode, I ran
ps aux | grep tsserverand saw 7+ orphanedtsserverprocesses 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:
-
detached: truewhen spawning → makes the process a group leader -
Shell.killTree()on shutdown → kills the group with-pidand escalates to SIGKILL
You're right that without detached: true, the group kill won't work - that's why the PR adds both together.
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