Skip to content

git bash.exe processes on Windows get orphaned #5525

Closed

Description

@nikitakot

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

Activity

  1. added
    opentuiThis relates to changes in v1.0, now that opencode uses opentui
    on Dec 14, 2025
  2. github-actions commented on Dec 14, 2025

    @github-actions
    Contributor

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

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

  3. self-assigned this
    on Dec 14, 2025
  4. removed
    opentuiThis relates to changes in v1.0, now that opencode uses opentui
    on Dec 14, 2025
  5. anntnzrb commented on Dec 21, 2025

    @anntnzrb
    Contributor

    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)

  6. anntnzrb commented on Dec 21, 2025

    @anntnzrb
    Contributor

    Opened PRs to address this:

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

  7. rekram1-node commented on Dec 21, 2025

    @rekram1-node
    Collaborator

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

  8. anntnzrb commented on Dec 21, 2025

    @anntnzrb
    Contributor

    @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!

  9. rekram1-node commented on Dec 21, 2025

    @rekram1-node
    Collaborator

    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

  10. anntnzrb commented on Dec 21, 2025

    @anntnzrb
    Contributor

    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.

  11. rekram1-node commented on Dec 21, 2025

    @rekram1-node
    Collaborator

    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 up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions