Skip to content

Kill sidecar before Windows update install to fix conpty.node lock#119

Merged
nedtwigg merged 3 commits into
mainfrom
repro
May 29, 2026
Merged

Kill sidecar before Windows update install to fix conpty.node lock#119
nedtwigg merged 3 commits into
mainfrom
repro

Conversation

@nedtwigg
Copy link
Copy Markdown
Member

Problem

On Windows, the auto-update installer failed at quit time with:

Error opening file for writing: …\Dormouse Terminal\_up_\sidecar\node_modules\node-pty\prebuilds\win32-x64\conpty.node

update.install() launches the NSIS installer, which overwrites the bundled sidecar's node_modules — including node-pty's native conpty.node. Windows refuses to overwrite a native module that a live process still has loaded, and the Node sidecar was still running when NSIS reached that file. The existing RunEvent::Exit sidecar kill is both too late and asynchronous — NSIS starts copying files immediately after install() force-kills the app, racing the sidecar's teardown. (The auto-update spec already flagged this exact gap.)

Fix

  • standalone/src/updater.ts — before install(), on Windows only, the close handler now awaits invoke('kill_sidecar_now') so the conpty.node handle is released before NSIS runs.
  • standalone/src-tauri/src/lib.rskill_sidecar_now is now synchronous: it sends the kill, then polls try_wait (capped at ~5s) until the process has actually exited. try_wait is used rather than the job-object wait() because wait() consumes a completion-port message the reaper thread relies on and could block forever if the sidecar had already exited.

macOS/Linux can replace open files in place, so they keep relying on the existing RunEvent::Exit cleanup — the new step is Windows-gated.

Cleanup (second commit)

  • Promoted IS_WINDOWS into lib/platform (next to IS_MAC) and reused it in updater.ts instead of an ad-hoc regex; deduped the duplicate copy shell-escape.ts carried.
  • Named the kill-wait loop constants (POLL_INTERVAL, MAX_POLLS) so the "~5s" cap is self-documenting.

Testing

  • updater.test.ts (17 tests) — added a test asserting the kill is invoked and completes before install on Windows.
  • shell-escape.test.ts (20 tests) — updated mocks for the shared constant; all pass.
  • cargo build + cargo test clean; both packages typecheck.
  • Manually verified on Windows: the update now installs successfully.

Note: this only takes effect from a packaged app (dev mode skips install()), so end-to-end verification requires a real Windows update.

🤖 Generated with Claude Code

nedtwigg and others added 2 commits May 29, 2026 10:05
The NSIS auto-update installer failed with "Error opening file for
writing: ...\_up_\sidecar\node_modules\node-pty\prebuilds\win32-x64\
conpty.node". Windows refuses to overwrite a native module that a live
process still has loaded, and the Node sidecar was still running when
NSIS reached node_modules. The RunEvent::Exit kill was too late and
async, racing NSIS file copies after install() force-kills the app.

The updater close handler now invokes kill_sidecar_now and awaits it
before install() on Windows. That command is synchronous on the Rust
side: it sends the kill, then polls try_wait (capped ~5s) until the
process has actually exited and released its handles. try_wait avoids
the job-object wait()'s completion-port double-consume hang.

macOS/Linux can replace open files in place, so they skip this.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cleanup pass over the conpty.node fix:

- Promote IS_WINDOWS to lib/platform (next to IS_MAC) and reuse it in
  updater.ts instead of an ad-hoc /Win/i regex; dedupe the duplicate
  copy that shell-escape.ts carried.
- Name the kill_sidecar_and_wait loop constants (POLL_INTERVAL,
  MAX_POLLS) so the "~5s" cap is self-documenting.

No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 29, 2026

Deploying mouseterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: 767a852
Status: ✅  Deploy successful!
Preview URL: https://16c5469a.mouseterm.pages.dev
Branch Preview URL: https://repro.mouseterm.pages.dev

View logs

@nedtwigg
Copy link
Copy Markdown
Member Author

@dormouse-bot fix the merge conflict and push to this branch

# Conflicts:
#	lib/src/lib/platform/index.ts
#	lib/src/lib/shell-escape.test.ts
@dormouse-bot
Copy link
Copy Markdown
Collaborator

Merged origin/main into repro. Two conflicts, both trivial:

  • lib/src/lib/platform/index.ts — both branches added an IS_WINDOWS export. The regexes (/win/i vs /Win/i) are equivalent under the /i flag; I kept main's docstring (the longer one, mentioning win32-input-mode/ConPTY/Codex).
  • lib/src/lib/shell-escape.test.ts — the importShellEscape(...) test helper diverged: this branch took { isMac, platform } and recomputed IS_WINDOWS from a regex; main took { isMac, isWindows } directly. The call sites later in the file already pass { isMac, isWindows }, so main's signature is the one that matches the rest of the file; I kept that.

standalone/src-tauri/src/lib.rs auto-merged cleanly — the kill_sidecar_and_wait helper and kill_sidecar_now command are intact.

Pushed as 767a852. Watching CI.

@nedtwigg nedtwigg merged commit 1929450 into main May 29, 2026
4 checks passed
@nedtwigg nedtwigg deleted the repro branch May 29, 2026 18:15
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.

2 participants