Skip to content

fix(windows): avoid terminating unrelated app instances - #358

Merged
PathGao merged 1 commit into
sftwrdotdev:masterfrom
PathGao:fix/windows-uninstall-safety
Aug 2, 2026
Merged

fix(windows): avoid terminating unrelated app instances#358
PathGao merged 1 commit into
sftwrdotdev:masterfrom
PathGao:fix/windows-uninstall-safety

Conversation

@PathGao

@PathGao PathGao commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Uninstall now waits only for the invoking Markpad process to exit before removing its installation directory. It no longer force-terminates every Markpad.exe process, so portable copies and separately opened Markpad instances keep running.

Validation

  • cargo test (23 tests)
  • npm test (142 tests)
  • npm run check (0 errors; one existing Settings modal a11y warning)

@PathGao
PathGao merged commit 67a93ee into sftwrdotdev:master Aug 2, 2026
5 checks passed
@PathGao
PathGao deleted the fix/windows-uninstall-safety branch August 2, 2026 17:10
PathGao added a commit that referenced this pull request Aug 2, 2026
Both flows could leave a machine in a state with no supported way out.

**Uninstall removed the registry entries first.** An unelevated uninstall of
a Program Files install deleted the Add/Remove Programs key and the
shortcuts, then spawned a batch script whose every `del` and `rmdir` was
silenced with `> nul 2>&1`. The files stayed on disk, the app was gone from
Add/Remove Programs, and `handle.exit(0)` ran regardless — `uninstall_app`
always returned `Ok(())`, so nothing was ever reported. A deletability
precheck now runs before anything is touched, the script logs each step to a
temp file instead of discarding the output, and it restores the uninstall
entry if deletion fails. Registry cleanup happens last.

**Install had no rollback.** The executable was copied at step 2, but the
Add/Remove Programs entry was not written until step 6. Any failure in
between — most likely creating a shortcut in a locked-down Public Desktop —
aborted the `?` chain with the exe in place, so `is_installed()` reported
true by path match and the installer UI never appeared again, while nothing
had ever been registered. Elevation is now checked before the first
mutation, and completed steps are undone in reverse on failure. An
update/repair never deletes what it was updating.

The script keeps the process handling introduced by #358: it waits for its
own process id to disappear and never matches on the image name, so a
portable copy, another user's session, or a second window with unsaved work
survives an uninstall. That wait is now bounded — reaching the deletion
attempt, which logs and restores the uninstall entry, beats an invisible
batch file spinning forever.

Also in these paths:

- `restore_file_association` opened keys with `create_subkey`, so
  uninstalling materialised `HKLM\Software\Classes\.md` and `.markdown` on
  machines where Markpad had never registered them, then deleted a default
  value that did not exist. It now opens read/write and returns early when
  the key is absent. The other hive is cleaned too, but only when no
  installation is registered there — otherwise an all-users uninstall would
  break a coexisting per-user install and leave `.md` pointing at a ProgID
  that no longer exists.
- The helper process was launched by bare name, so the process search order
  checked the application and current directories before `%PATH%` — a
  planted `wscript.exe` would run with whatever privileges the uninstall
  had. It, and `tasklist`, `find`, `timeout` and `reg` inside the batch, are
  now resolved through `%SystemRoot%\System32`. The temp script names carry
  random suffixes and are created with `create_new` instead of being fixed
  and overwritable.
- Permission failures return an `ELEVATION_REQUIRED:` code. The installer UI
  matched on the literal string `Access is denied`, which only appears on
  English Windows, so localized systems showed the raw exception instead of
  the "run as administrator" message.

The Windows-only logic is extracted into pure functions gated
`cfg(any(target_os = "windows", test))` so the ordering, rollback and script
generation are exercised by tests on any host.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant