Skip to content

docs: Reliable workaround for PM2 CLI argument parsing / terminal popup issues on Windows #6124

Description

@user4302

Context

Many Windows users run into parsing, shell-escaping, or interactive child-process window popups when trying to run active development servers (like npm run dev) via PM2 on Windows shells. This friction has been heavily discussed across several threads, including:

Instead of trying to patch complex string-escaping layers or breaking Hot Module Replacement (HMR) by forcing production builds to hide terminal windows, a highly reliable workaround is to bypass third-party wrappers entirely and lean into native Windows process configuration.


The Raw Workaround: Independent Hidden PowerShell Processes

While PowerShell's Start-Job works asynchronously, it is explicitly bound to its parent terminal lifecycle and kills the server when that terminal window closes.

To run a server completely invisibly and allow the calling terminal window to be closed safely without tearing down the process, you can decouple it by forcing a hidden background console title that remains readable to standard lookup queries.

1. Start the Process Globally

Run the following from your project directory. This sets a readable console window title string before launching your server toolchain:

$script = "`$host.UI.RawUI.WindowTitle='devbg_id:my-app'; npm run dev"
$encoded = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($script))

$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = "powershell.exe"
$psi.Arguments = "-NoExit -EncodedCommand $encoded"
$psi.WorkingDirectory = $pwd.Path
$psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
$psi.CreateNoWindow = $true

[System.Diagnostics.Process]::Start($psi)

2. Inspect & Terminate

Because the window interface is entirely hidden, you can identify and terminate it cleanly using its custom console title handle:

# Find the hidden process ID via its assigned title identifier
(Get-Process -Name "powershell" | Where-Object { $_.MainWindowTitle -eq "devbg_id:my-app" }).Id

# Halt the environment execution loop gracefully
Stop-Process -Id <Process_Id> -Force

The Streamlined Solution: dev-bg Process Manager

Because manually encoding strings and scanning window titles gets tedious, I compiled this window-title and base64 tracking flow into a single-command utility script wrapper that acts like a lightweight, Windows-native tool.

It automatically infers your active folder context name, handles the hidden process handshakes natively, safeguards against non-Node directories, and cleans up after itself seamlessly without terminal clutter.

If anyone else wants to use or adapt the script wrapper to simplify their workflow, the source code is hosted here:
👉 dev-bg on GitHub

Streamlined Command Reference:

  • dev-bg start — Encodes parameters and fires npm run dev inside an independent hidden window bound to the folder name.
  • dev-bg list — Scans active process window titles to display a dashboard of running background daemons.
  • dev-bg stop — Resolves the specific target identifier and kills the process tree cleanly.
  • dev-bg remove — Halts the target process and purges any trace logs completely.

Intended Value

Cross-referencing these native workarounds and tools in documentation recipes or attaching them to active Windows triage pipelines would immensely help web developers looking for a painless background workspace experience on Windows!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions