fix(uninstall): edit the Windows user PATH via the registry and broadcast best-effort - #1403
Merged
Merged
Conversation
…cast best-effort
[Environment]::SetEnvironmentVariable('Path', ..., 'User') writes HKCU\Environment
and then broadcasts WM_SETTINGCHANGE synchronously to every top-level window. On a
machine with a hung GUI process that broadcast can outlast the uninstaller's 15 s
command timeout, so PowerShell was killed (exit 124) and the uninstaller refused
to remove the CLI even though the PATH edit had completed.
The remove and restore scripts now read the user Path unexpanded from the
registry, preserve its value kind, write it back through Microsoft.Win32.Registry,
and only then broadcast WM_SETTINGCHANGE via SendMessageTimeout with
SMTO_ABORTIFHUNG and a 1 s per-window timeout inside try/catch, exiting 0
explicitly so the broadcast can never affect the exit code. The exit-3
not-present contract and the JSON rollback echo are unchanged.
A genuine registry failure still pushes an error and preserves the CLI; a
timeout is now reported as such. Because the rollback echo is written only after
a successful write, a timeout that arrives after the echo is treated as a
completed edit with a warning instead of a failure.
… process ends The remove script prints the rollback JSON, and the restore script now prints a PLANNOTATOR_PATH_RESTORED sentinel, only after the registry write. A native fault inside Add-Type or SendMessageTimeout cannot be caught by try/catch and ends the process with an NTSTATUS exit code, so both callers now treat a parseable echo as proof of the completed edit whether the process timed out or exited non-zero for another reason, and warn about the broadcast instead of misreporting a finished edit as a failure (which would also lose the rollback value). A non-zero exit with no echo still fails closed.
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.
The failure
The "Smoke-test uninstall lifecycle" step of the
smoke-binariesjob failed three times in a row on one Windows run withCould not remove ...\AppData\Local\plannotator from the Windows user PATH.while passing on other runs. That run's cleanup also killed several orphanedmsedgeprocesses.The mechanism
WINDOWS_PATH_SCRIPTremoved the entry with[Environment]::SetEnvironmentVariable('Path', $n, 'User'). That .NET call writesHKCU\Environmentand then broadcastsWM_SETTINGCHANGEto every top-level window withSendMessageTimeout, synchronously, one window at a time, withoutSMTO_ABORTIFHUNG. On a runner with hung GUI processes the broadcast can outlast the uninstaller's 15 s command timeout indefaultRunCommand, which kills PowerShell and returns exit 124. The uninstaller then correctly refused to proceed, even though the registry edit had already completed. A real user with one hung window can hit the same stall.The fix
Both PowerShell one-liners (
WINDOWS_PATH_SCRIPTandWINDOWS_PATH_RESTORE_SCRIPTinpackages/server/uninstall.ts) now edit the registry directly and broadcast separately:PathfromHKCU\EnvironmentwithDoNotExpandEnvironmentNamesso%VARS%are never expanded, and capture its value kind (REG_EXPAND_SZon most systems).Microsoft.Win32.Registrywith the same kind, keep the exit-3 "not present or unchanged" contract, and echo the original value as JSON on stdout for rollback.WM_SETTINGCHANGEbest-effort: a P/InvokeSendMessageTimeoutwithSMTO_ABORTIFHUNGand a 1000 ms per-window timeout, insidetry/catch, followed by an explicitexit 0, so nothing about the broadcast can reach the exit code.REG_EXPAND_SZif the value is gone, and broadcasts the same way.DllImportattribute's double quotes are assembled from[char]34at runtime because the script travels as one-Commandargv element.Shared broadcast statements live in
WINDOWS_PATH_BROADCAST_STATEMENTS.What stays fail-closed
command timed outfromexit N, aligned with the host-command wording used elsewhere in the file.SetValue, a timeout that arrives after a parseable echo is now treated as a completed edit, with a warning that notifying open windows timed out. The rollback value is available in that case, so the self-delete and restore paths behave exactly as after a clean exit 0.Two-runtime note
Uninstall is Bun-only (the compiled binary).
apps/pi-extensionhas no mirror of this code: a grep forSetEnvironmentVariableanduninstallfinds only a README mention. Nothing in CLAUDE.md or the marketing docs describes the Windows uninstall PATH handling, so no docs changed.Testing
bun run typecheck: clean.bun test packages/server/uninstall.test.ts: 41 pass, 0 fail. New tests cover the exit-3 unchanged path, a timeout before the echo (error, CLI preserved), a timeout after the echo (removed with a warning), the no-double-quote and no-SetEnvironmentVariableinvariants with echo-before-broadcast ordering, and a real PowerShell parse of both scripts viaParser.ParseInput(parse only, no execution, no registry access). The parse test runs on the existinguninstall-windowsCI job and skips when no PowerShell is found; locally it was run against a portable pwsh 7.4.6 throughPLANNOTATOR_TEST_POWERSHELL.bun test packages/server: 758 pass, 1 skip, 7 fail. The 7 are the known sem sidecar sandbox failures (review-workspacesemantic diff API and workspace integration,agent-terminal-bridgenode sidecar spawn) and are unrelated to this change.AI-assisted (Claude) under maintainer direction.