Skip to content

[Windows] powershell tool session stays running indefinitely when a script spawns a background process with Start-Process -RedirectStandardOutput #5387

Open

Description

@y-wan

Describe the bug

On Windows, when a powershell tool command runs a script that spawns a long-lived background process using Start-Process ... -RedirectStandardOutput <file> -RedirectStandardError <file> (especially when the outer command is piped, e.g. pwsh -NoProfile -File start-server.ps1 2>&1 | Tee-Object -FilePath run.log), the child script finishes and exits normally, but the outer pwsh.exe session managed by the powershell tool stays in status: running indefinitely and prevents the session from becoming idle.

Furthermore, if stop_powershell is called on that shellId to clean up the hung outer pwsh.exe wrapper, stop_powershell terminates the entire process tree—including the newly spawned background service that was intended to stay alive.

Root cause:

  1. On Windows/.NET, Start-Process with -RedirectStandardOutput / -RedirectStandardError invokes CreateProcessW(..., bInheritHandles: TRUE) without a handle allowlist (PROC_THREAD_ATTRIBUTE_HANDLE_LIST).
  2. Any inheritable pipe write handles in the parent pwsh.exe (such as the stdout/stderr pipe created by the powershell tool runner or an outer | Tee-Object pipeline) are inherited by the long-lived grandchild process.
  3. Even after the direct child script exits, the surviving grandchild process keeps the inherited pipe write handle open, so the outer stream reader / Tee-Object never receives EOF.
  4. Because the powershell tool waits for stream EOF and stop_powershell kills the entire process tree, there is no built-in way to finalize the completed shell wrapper without killing the background process it started.

Affected version

1.0.102 (also reproducible on 1.0.107)

Steps to reproduce the behavior

  1. On Windows, run a powershell tool command in sync mode that invokes a child PowerShell script through Tee-Object:
    pwsh -NoProfile -File .\start-service.ps1 2>&1 | Tee-Object -FilePath .\start.log
  2. Inside start-service.ps1, spawn a long-lived background process with redirected log files and then exit:
    Start-Process -FilePath "node.exe" -ArgumentList "-e `"setInterval(() => {}, 1000)`"" `
        -WindowStyle Hidden -RedirectStandardOutput ".\service.out.log" -RedirectStandardError ".\service.err.log" | Out-Null
    Write-Output "Service started"
  3. Observe that "Service started" is printed and the inner pwsh script process exits immediately, but list_powershell continues to show the shellId in status: running indefinitely after initial_wait expires.
  4. Calling stop_powershell on that shellId terminates the background node.exe process as well.

Expected behavior

  1. Once the direct command/script process exits, the powershell tool should not remain blocked indefinitely waiting on stdout/stderr pipe handles inherited by detached/background grandchild processes.
  2. Alternatively, stop_powershell (or the shell runner) should provide a way to close/reap the finished outer shell wrapper without terminating intended background grandchild processes.

Additional context

  • Operating system: Windows 11 (x64)
  • CPU architecture: x86_64
  • Shell: PowerShell 7 (pwsh.exe)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:platform-windowsWindows-specific: PowerShell, cmd, Git Bash, WSL, Windows Terminalarea:toolsBuilt-in tools: file editing, shell, search, LSP, git, and tool call behavior

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions