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.
Right
$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:
- Whether
wtlaunched from a user-context (vs system-context) parent - Whether the user’s persistent User PATH update has propagated (the
[Environment]::SetEnvironmentVariable("PATH", ..., "User")call only affects NEW shells launched after the broadcast, not in-flight wt children) - Whether the shortcut’s
WindowStyle Hiddenaffects environment propagation (it does in some cases)
The same pattern bit us on:
pnpm devin the OmniRoute startup tab (2026-06-30)gptme,sgpt,oterm,vibein tabs spawned before the User PATH update propagated (2026-06-30)pwshin the initial Windows Terminal tab batch (pwsh isn’t installed;powershell.exeworks)
How to write a script that doesn’t break
-
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' -
Quote with single-quotes around the absolute path so PowerShell doesn’t try to expand
$inside the path:$inner = "& '$pnpm' dev" -
Use
-EncodedCommandinstead of-Commandwhen launching viawt new-tab ... pwsh .... The-Commandarg goes through wt’s tokenizer plus PowerShell’s argument parsing — nested quotes + spaces in paths (Program Files) routinely break.-EncodedCommandaccepts 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 $wtArgsFailure 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
& '...' devstring as the binary name. Encoded-command bypasses the parser entirely. -
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
- Direct invocation in the running session (
pnpm devtyped by you in this terminal) - Scripts invoked by your interactive shell (not by
wt new-tab ... -Command "...") - Background tasks spawned by Task Scheduler with the right user-context flag
For everything that goes through wt’s argument-string command spawning, use absolute paths.
Anti-patterns to flag in code review
wt new-tab ... powershell -Command "<bareword-binary> ..."→ flag immediately, use full pathStart-Process -FilePath "<bareword-binary>"from a script meant to run pre-login → flag- Shortcut
.lnkfiles where the Target iscmd.exeorpowershell.exeand the Arguments call binaries by short name → flag
See also
globals-derived-from-workspace— related; covers the User PATH sidejunctions-on-windows— sibling Windows gotcha rule- Failure mode reproduced + fixed:
decisions/agent-tooling/omniroute-dev-server-from-source-2026-06-30§“Path note for shell:startup”