fix(windows): zap crashes on PowerShell 7.6 at startup - #327
Open
Q-xuan wants to merge 5 commits into
Open
Conversation
added 5 commits
July 27, 2026 22:53
On PS 7.6 + -NoProfile (which zap always uses to spawn pwsh), PSReadline
isn't auto-loaded into the runspace anymore. The unconditional
Remove-Module -Name PSReadline
on line 2 of pwsh_init_shell.ps1 then throws a terminating error, the
init script dies before the prompt function runs, zap never gets the
InitShell hook, and pwsh exits with code 0 — so every new tab shows
"Shell process exited prematurely!".
Repro:
pwsh -NoProfile -Command "Remove-Module -Name PSReadline"
-> Remove-Module: No modules were removed. ...
Add -ErrorAction Ignore so it's a no-op when PSReadline isn't loaded.
Verified locally: with the fix the InitShell OSC fires and pwsh stays
interactive. Safe on older PS versions too (no-op when the module *is*
loaded, which is the normal interactive case).
The Remove-Module fix in the previous commit wasn't enough. The real
crash on PS 7.6 is a parser error:
ParserError:
$global:_warpSessionId = [int64]\"$epoch$random\"
~~~~~~~~~~~
Unexpected token '$random' in expression or statement.
zap's append_quoted (windows/mod.rs) backslash-escapes " → \" per the
Windows argv-splitting convention, which is correct for cmd.exe-style
consumers. But PowerShell's -Command parser does NOT honour \": on
PS 7.6 the stray \" tokens trip the parser, the whole init script
fails to parse, pwsh exits with code 0 before emitting the InitShell
OSC, and zap shows "Shell process exited prematurely" with an empty
Warpify output panel.
PS 7.5 and earlier tolerated this; PS 7.6's stricter tokenizer doesn't.
Switch the PowerShell branch to -EncodedCommand (UTF-16LE base64). The
payload is opaque to both the Windows argv splitter and the PS
tokenizer, so the quoting bug is sidestepped entirely on every PS
version. Cost is ~3-4x larger argv (~4.4k chars for the current init
script), still far under CreateProcess's 32k limit.
Verified locally: -EncodedCommand + -NoExit keeps pwsh interactive and
the init script parses cleanly (no ParserError). The Remove-Module
-ErrorAction Ignore from the previous commit is still needed (PS 7.6
also stopped auto-loading PSReadline under -NoProfile, so the
unconditional Remove-Module throws if not silenced).
Diagnose PS 7.6 exit-1 crash. Writes the exact command line (plus per-arg breakdown) passed to CreateProcessW to %TEMP%\zap_shell_command.log on every shell spawn.
The UTF-16LE blob passed to pwsh -EncodedCommand had a trailing NUL character (U+0000) appended via .chain(std::iter::once(0)). PowerShell 7.6 treats this NUL as script content, causing the init script to error at runtime and pwsh to exit with code 1. Verified: base64 without the NUL → pwsh exit 0 (success); base64 with the NUL → pwsh hangs/crashes. Also removes the temporary debug logging in shell_command() that wrote the command line to %TEMP%\zap_shell_command.log.
jwp2987
pushed a commit
to jwp2987/phosphor
that referenced
this pull request
Jul 29, 2026
PowerShell 7.6's script-block parser chokes on some quoting shapes passed
via -Command, killing the shell process immediately after launch ("Shell
process exited prematurely"). Build the script as base64 UTF-16LE and pass
it via -EncodedCommand instead, which sidesteps the parser entirely.
Also make the PSReadline unload in the init script tolerant of the module
not being loaded (-ErrorAction Ignore instead of a hard failure).
Ports upstream zerx-lab#327.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
zap fails to start PowerShell 7.6 on Windows — every new tab shows "Shell process exited prematurely!" within ~1s and the log records
shell pty child exited: exit_code=0.Why
zap spawns pwsh with
-NoProfile(app/src/terminal/local_tty/shell.rs, PowerShell branch). On PS 7.6 PSReadline is no longer auto-loaded into the runspace under-NoProfile, so the unconditionalon line 2 of
pwsh_init_shell.ps1throws a terminating error. The whole init script is passed via-Command, so this kills the script before thepromptfunction (which emits theInitShellOSC) ever runs. pwsh exits 0, zap never gets the hook, marks the shell as dead.Repro outside zap:
Fix
-ErrorAction Ignore. One line:IgnoreoverSilentlyContinue: suppresses the error record entirely — no$Errorpollution, noWrite-Errorleaking into the PTY.Verified
With the fix the
InitShellOSC (\e]9278;d;<hex>\a) fires and pwsh stays interactive instead of exiting.zap.logno longer shows the premature exit.Safe on other PS versions
-ErrorAction Ignoreonly changes behavior when PSReadline is not loaded — exactly the broken case. When it is loaded (older PS 7.x, normal interactive launches) the module is still removed as before. Same line exists verbatim inwarpdotdev/warp(app/assets/bundled/bootstrap/pwsh_init_shell.ps1, commit32d21d15c), so this will hit more users as PS 7.6 adoption grows.