type: rule
status: active
timestamp: 2026-06-30
tags: [windows, powershell, startup-scripts, path, gotcha]

Windows shortcuts/wt-spawned shells: use absolute paths to .cmd/.exe binaries

Windows Terminal tabs launched via wt new-tab + a -Command string don't reliably inherit the User PATH. Always use the absolute path to npm.cmd / pnpm.cmd / python.exe etc. when constructing startup scripts.

Use absolute paths to binaries in Windows shortcuts and wt-spawned shells

The rule

In any script that launches a child PowerShell via wt new-tab ... powershell -Command "..." or from shell:startup, hard-code the absolute path to every binary the inner command calls. Don’t rely on PATH lookup for pnpm, npm, python, git, pip, code, etc.

$pnpm = 'C:\Program Files\nodejs\pnpm.cmd'
$inner = "& '$pnpm' dev"
Start-Process wt -ArgumentList "new-tab ... powershell -Command `"$inner`""

Wrong

$inner = "pnpm dev"  # ← will fail with 'command not found' in the spawned tab
Start-Process wt -ArgumentList "new-tab ... powershell -Command `"$inner`""

Why this fails

Symptom seen 2026-06-30 with OmniRoute startup:

[error 2147942402 (0x80070002) when launching ” pnpm dev”‘] The system cannot find the file specified.`

The spawned PowerShell tab inherits Machine PATH but NOT the User PATH that contains C:\Program Files\nodejs (or AppData\Roaming\npm, or the Python user-scripts dir). The exact subset of PATH that gets inherited depends on:

The same pattern bit us on:

How to write a script that doesn’t break

  1. Resolve the binary path once at script start:

    $pnpm = (Get-Command pnpm).Source  # works at script-author time
    # OR hard-code from `where.exe pnpm` output:
    $pnpm = 'C:\Program Files\nodejs\pnpm.cmd'
  2. Quote with single-quotes around the absolute path so PowerShell doesn’t try to expand $ inside the path:

    $inner = "& '$pnpm' dev"
  3. Use -EncodedCommand instead of -Command when launching via wt new-tab ... pwsh .... The -Command arg goes through wt’s tokenizer plus PowerShell’s argument parsing — nested quotes + spaces in paths (Program Files) routinely break. -EncodedCommand accepts a base64-encoded UTF-16LE blob that wt passes verbatim. Escape-proof.

    # The pattern that actually works:
    $bytes   = [System.Text.Encoding]::Unicode.GetBytes($inner)
    $encoded = [Convert]::ToBase64String($bytes)
    $wtArgs  = "new-tab --title `"X`" -d `"$dir`" `"$psPath`" -NoExit -EncodedCommand $encoded"
    Start-Process wt -ArgumentList $wtArgs

    Failure mode without -EncodedCommand:

    [error 2147942402 (0x80070002) when launching ” & ‘C:\Program Files\nodejs\pnpm.cmd’ dev”‘] The system cannot find the file specified.`

    Windows interprets the entire quoted & '...' dev string as the binary name. Encoded-command bypasses the parser entirely.

  4. For binaries with no fixed install path (user-scoped pip CLIs, npm globals), pre-resolve and check existence:

    $tool = "$env:APPDATA\npm\my-tool.cmd"
    if (-not (Test-Path $tool)) { throw "Tool not installed at expected path" }

When PATH-lookup DOES work

For everything that goes through wt’s argument-string command spawning, use absolute paths.

Anti-patterns to flag in code review

See also


Edit on GitHub · Back to index