fix(desktop): verified update teardown on Windows+WSL, install integrity check, and resilient WSL readiness handshake - #5998

Open
Jgratton24 wants to merge 9 commits into
pingdotgg:mainfrom
Jgratton24:fix/update-apply-wsl-handshake
Open

fix(desktop): verified update teardown on Windows+WSL, install integrity check, and resilient WSL readiness handshake#5998
Jgratton24 wants to merge 9 commits into
pingdotgg:mainfrom
Jgratton24:fix/update-apply-wsl-handshake

Conversation

@Jgratton24

@Jgratton24Jgratton24 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What Changed

Two Windows+WSL reliability fixes that share one user-visible symptom (an indefinite "Connecting to WSL…" splash), plus a guard that turns the corrupted-install crash into an actionable dialog. Desktop-only; no apps/server changes.

1. Update apply is verified before the installer runs (fixes the half-applied-update corruption).

  • DesktopBackendInstance.stop() now reports whether the child process actually finalized within the timeout instead of silently swallowing it (DesktopBackendManager.ts).
  • The install path (DesktopUpdates.ts) aborts — restarting backends and surfacing the error through the update state machine — if any backend can't be verified stopped, instead of handing off to NSIS while a process still holds files under the install dir.
  • For WSL backends, a stopped wsl.exe relay does not mean the Linux-side server (or helpers it spawned, e.g. cloudflared) exited with it. New ensureWindowsPathReleased (DesktopWslEnvironment.ts) terminates and verifies from inside the distro that no Linux process still holds cwd/exe/fd/mmap references under /mnt/c/<install dir> — 9p/DrvFs handles held from WSL block file replacement on the Windows side, which is how app.asar and app.asar.unpacked end up from different builds.
  • Post-apply integrity check: the build stamps apps/server/dist/desktop-build-manifest.json (under app.asar.unpacked on Windows); startup compares it against the asar's version and refuses to launch a mixed-build install with a clear repair dialog (DesktopInstallIntegrity.ts) instead of the opaque ERR_MODULE_NOT_FOUND main-process crash. Missing manifest (dev, macOS/Linux, pre-stamp installs) skips the check, so no false positives.

2. WSL endpoint detection and the readiness handshake are no longer single-point-of-failure.

  • Networking mode now comes from wslinfo --networking-mode (first-party, present on WSL 2.6.x and 2.7.x) instead of being inferred from the first address in hostname -I — whose ordering shifts whenever Tailscale, Docker bridges, or VPN adapters exist inside the distro. Heuristic fallback for older WSL matches any distro IP against Windows interfaces, never position one, and CGNAT 100.64/10 addresses are never advertised.
  • Readiness probes race loopback plus every enumerated distro address; the first candidate that answers wins and becomes the advertised base URL — so a wrong guess degrades startup by nothing worse than a probe race.
  • After all static candidates time out, the backend's persisted server-runtime.json origin is consulted as a last-resort candidate (gated on it naming this instance's port).
  • The readiness probe is no longer one-shot: a run that spawns but never answers is terminated and retried, and after 3 readiness timeouts the failure surfaces like an exhausted preflight (dialog + Windows fallback on the primary, inline Connections error on the secondary) with every probed URL named. Resolved mode and candidates are logged at startup.

25 new tests across DesktopBackendManager, DesktopUpdates, DesktopWslEnvironment, DesktopBackendConfiguration, and DesktopInstallIntegrity cover the regressions above (CGNAT-first ordering, docker0-first, wslinfo absent, one-shot-probe limbo, unverified stop, WSL busy at install time, mixed-build detection). Full desktop suite: 473 passing.

Why

Two field incidents on 0.0.33-nightly builds, plus these open issues:

The half-applied update we debugged: an in-app update left app.asar + the exe at the old build while app.asar.unpacked/node_modules was new → ERR_MODULE_NOT_FOUND on every launch, with the updater having reported nothing. Root cause: install teardown killed the wsl.exe relay and proceeded on a 5s timeout that was silently swallowed, while the Linux-side server exited asynchronously still holding 9p handles into the install dir. Same version installed fine with the app closed.

Sizing note: aware of the contribution guidelines — roughly half of this diff is tests, and the two fixes share the same touched files (DesktopBackendManager/DesktopWslEnvironment), which is why they're one PR. Happy to split into updater-atomicity and handshake PRs if you'd prefer.

This PR deliberately avoids every file touched by #2829 (orchestrator v2) — verified against its full file list, so it merges cleanly before or after.

UI Changes

No renderer UI changes. Two native dialogs are added/extended (update-install-aborted error via the existing update state machine; "installation is damaged" message box with an Open Download Page button). Not screenshotted — they require a packaged Windows build to trigger, which this dev environment can't produce; dialog copy is in DesktopInstallIntegrity.ts and DesktopUpdates.ts.

Checklist

  • This PR is small and focused (two related reliability fixes, no feature work; ~half the diff is tests)
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (native dialogs only; see UI Changes)
  • I included a video for animation/interaction changes (n/a)

🤖 Generated with Claude Code


Note

High Risk
Changes touch critical Windows update apply, WSL process killing (root), and startup abort paths; incorrect readiness or release checks could block launches or updates, though behavior is heavily tested.

Overview
This PR targets two reliability problems on Windows with WSL—indefinite “connecting” and half-applied updates—and adds a startup guard for corrupted installs.

WSL backend reachability no longer trusts the first hostname -I address. Networking mode comes from wslinfo --networking-mode (with heuristics when unknown), CGNAT 100.64/10 IPs are never advertised, and readiness races loopback plus every distro IP; the winning URL is rebound into currentConfig. After static probes time out, server-runtime.json supplies fallback candidates. Runs that never become ready are terminated and retried, with caps that surface failures via onPreflightFailed instead of hanging forever. getDistroIp is replaced by getDistroIps.

Updates require each backend stop() to return whether the child actually exited within a 10s budget; failed installs abort, restart backends, and leave the update downloadable. On Windows, ensureWindowsPathReleased verifies WSL has released the install directory (released / busy / unknown) before quitAndInstall.

Install integrity compares the packaged app version to desktop-build-manifest.json under app.asar.unpacked (stamped at build time); mismatches or a missing manifest on packaged Windows show a repair dialog and exit before bootstrap. The build script writes the manifest into staged apps/server/dist.

Reviewed by Cursor Bugbot for commit 249a6d9. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix update teardown on Windows+WSL with install integrity checks and resilient WSL readiness handshake

  • Startup now runs an install-integrity check via DesktopInstallIntegrity before bootstrapping; on Windows, a desktop-build-manifest.json stamped at build time is compared against the app version and, on mismatch, shows an error dialog and quits.
  • The update install flow in DesktopUpdates.ts now requires each running backend to confirm a finalized stop within 10 seconds and, on Windows+WSL, calls ensureWindowsPathReleased to verify the install directory is free before invoking quitAndInstall; on failure it restarts backends and surfaces an error.
  • WSL readiness probing in DesktopBackendManager.ts now races multiple candidate URLs (loopback + all distro IPs) and optionally consults persisted runtime-state fallback URLs before timing out; stop() now returns a boolean indicating whether the process finalized before the timeout.
  • DesktopWslEnvironment.ts replaces the single-IP getDistroIp with getDistroIps (all IPv4s), and adds getNetworkingMode, readServerRuntimeState, and ensureWindowsPathReleased with fixed timeouts and fallback semantics.
  • Risk: backends that do not finalize within 10 seconds during an update will block the installer and restart, changing prior best-effort stop behavior.

Macroscope summarized 249a6d9.

…iness handshake
Two user-facing failures shared one splash-hang symptom; both are fixed here.
Update apply (half-applied install):
- instance.stop() now reports whether the child fully finalized; the
install path aborts (and restarts backends, surfacing the error through
the update state machine) instead of running NSIS while a backend that
holds install-dir handles may still be alive.
- Before handing off to the installer on Windows, verify from inside the
distro that no Linux process still holds cwd/exe/fd/mmap references
under /mnt/c/<install dir> (ensureWindowsPathReleased: TERM, then KILL,
then report busy). 9p handles held from WSL block file replacement on
the Windows side, which is how app.asar and app.asar.unpacked ended up
from different builds.
- Post-apply integrity check: the build stamps
apps/server/dist/desktop-build-manifest.json (asar-unpacked on Windows);
startup compares it against the asar version and refuses to launch a
half-applied install with an actionable repair dialog instead of an
opaque ERR_MODULE_NOT_FOUND crash.
WSL readiness handshake (indefinite 'Connecting to WSL'):
- Networking mode now comes from wslinfo --networking-mode instead of
inferring it from the FIRST hostname -I address (a WSL-side tailscaled
putting a CGNAT 100.64/10 address first made mirrored hosts look NAT'd
and pointed every probe at an unroutable IP).
- Readiness races loopback and every enumerated distro address; the first
candidate that answers wins and becomes the advertised base URL.
- After all static candidates time out, the server's persisted
server-runtime.json origin is consulted as a last-resort candidate.
- A run that spawns but never answers any probe is terminated and retried;
after 3 readiness timeouts the failure surfaces like an exhausted
preflight (dialog + Windows fallback on the primary, inline error on the
secondary) with the probed URLs named, instead of sitting on the splash
forever. Resolved mode and candidates are logged at startup.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Aug 10, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e310a9a5-be8d-496e-b0cb-2823e06b141c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 10, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect service conventions review: one convention violation found — the new readiness fallback path uses Effect.catchTag, which the conventions replace with Effect.catchTags({ ... }) even when recovering a single tag. Everything else (subpath namespace imports, service-member additions on DesktopWslEnvironment with matching layerTest stubs, environment-based dependency acquisition in DesktopUpdates.make, Schema.TaggedErrorClass attributes with an attribute-derived message) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f49d4ea5f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/desktop/src/app/DesktopInstallIntegrity.ts Outdated
Comment threadapps/desktop/src/updates/DesktopUpdates.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
@macroscopeapp

macroscopeappBot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces significant new runtime behavior beyond a simple fix: a startup integrity check that can block app launch, fundamental changes to WSL backend connection logic with multi-candidate probing, new update install abort conditions when backends don't fully stop, and new capabilities to kill Linux processes holding files. The ~2200 additions span multiple subsystems and warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Jgratton24and others added 2 commits August 10, 2026 08:34
- Recover known tags with Effect.catchTags (convention) in the readiness
fallback path.
- Anchor the install-dir holder scan: descendants match "$dir/" and the
directory itself matches as an exact readlink line, so /mnt/c/application
is no longer classified as a holder of /mnt/c/app.
- Split the release-check result: "unavailable" (wsl.exe would not spawn —
no running VM can hold /mnt handles) proceeds with a warning, while
"unknown" (timeout/path-translation failure against a distro that was
hosting this backend moments ago) now aborts the install.
- abortInstall restarts only instances that were running before teardown,
so a previously-stopped backend is not resurrected by a failed attempt.
- readServerRuntimeState reads the state-dir flavor the spawned backend
actually uses (dev/ for --dev-url runs, userdata/ for packaged) instead
of preferring a possibly-stale packaged file.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ess surfacing
- A packaged Windows build with NO unpacked build manifest is now treated
as a half-applied update (new app.asar over a pre-stamp unpacked tree)
instead of skipping the check — every Windows artifact that ships the
checker also ships the stamp. Platforms that pack the server dist inside
the asar still skip.
- A non-fatal surfacing on a primary that is already the Windows backend
(only reachable via readiness exhaustion) now shows one dialog and stops
instead of applying a no-op WSL fallback and looping dialog + restart
forever.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadapps/desktop/src/app/DesktopInstallIntegrity.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Jgratton24and others added 2 commits August 10, 2026 08:48
…WSL release always aborts
- The integrity check now distinguishes read failures: only a NotFound on
packaged Windows is treated as a half-applied update. An unreadable
manifest (access denied, I/O error) logs and skips instead of falsely
bricking a healthy install.
- Drop the "unavailable" proceed path from the WSL release check: a
release-script spawn failure happens after wslpath already succeeded
against the distro, so it cannot prove WSL is gone — every unverified
outcome now reads "unknown" and aborts the install. A machine whose WSL
layer is genuinely broken recovers on the next launch via the preflight
Windows fallback (no WSL config in the pool means no release check).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nstead of restarting forever
Ports the still-relevant piece of pingdotgg#3623: a backend that spawns fine but
keeps exiting before it ever answers a readiness probe previously
restarted silently forever (500ms-10s backoff, no cap, no surfacing).
After 5 consecutive never-ready exits the failure now surfaces through
onPreflightFailed — dialog + Windows fallback on the primary, inline
Connections error on the WSL secondary — exactly like exhausted preflight
and readiness failures.
The counter is scoped strictly to post-spawn exits: pre-spawn preflight
retries have their own counter, a crash after the backend was ready
resets the streak (an established backend crashing is a different failure
than one that never comes up), and the budget refreshes on ready, on
external stop, and on a fresh manual start so one cap firing does not
permanently remove the retry budget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 10, 2026
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 02343e8. Configure here.

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
…r-ready exit cap
A readiness-timeout termination closes the run scope, and the resulting
child exit previously ALSO incremented exitFailureAttempt — every
readiness timeout double-counted as a crash, so after a readiness-cap
recovery the leftover streak could fire a second, misleading "exited N
times" dialog just two real failures later. The readiness kill now marks
the run stopRequested before closing its scope, which both excludes it
from the exit cap and routes finalize to discardSession (the failure log
was already captured via persistFailureSnapshot). Regression test drives
five readiness cycles with a retrying onPreflightFailed and asserts the
exit cap never fires.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Jgratton24
Jgratton24force-pushed the fix/update-apply-wsl-handshake branch from 8e4c156 to 61c09f0CompareAugust 10, 2026 13:54
Comment threadapps/desktop/src/backend/DesktopBackendPool.ts Outdated
Jgratton24and others added 2 commits August 10, 2026 10:06
…SL-primary condition
resolvePrimary only selects the WSL backend when BOTH wslOnly and
wslBackendEnabled are set (wslRequested), but the non-fatal surfacing
branch checked wslOnly alone. With wslOnly persisted true while
wslBackendEnabled is false, a Windows primary exhausting readiness
retries took the WSL branch: a misleading "WSL backend is still
unavailable" dialog, an unrelated in-memory WSL settings mutation, and
one wasted retry cycle before the stop branch finally fired. The guard
now mirrors resolvePrimary's own condition, so a Windows primary
surfaces the generic backend-unavailable dialog and stops immediately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live testing on a real Windows+WSL host (WSL 2.7, mirrored) showed that
plain Linux fds and mmaps through 9p/DrvFs do NOT block Windows-side file
replacement, while a Windows-side handle does (the backend child runs as
the app executable and reads through app.asar). Comments now describe the
WSL release check as the defensive guarantee it is, with the verified stop
of Windows backend children as the confirmed-mechanism fix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ensureWindowsPathReleased spawned the holder scan as the default WSL
user. find_holders inspects /proc/<pid>/{cwd,exe,fd,maps}, which the
kernel exposes only to the process owner and root, so the unprivileged
scan silently got EACCES for any process it did not own. A root- or
other-user-owned process holding the install directory was therefore
invisible: the check returned RELEASED without ever seeing (let alone
killing) it, defeating the guard in exactly the case it exists for.
Spawn the probe as `wsl -u root` (WSL grants root with no password,
gated by the Windows host) so the scan sees every holder and its kills
land on holders it does not own. find_holders is already scoped to
processes that reference the install dir, so this cannot reach
unrelated processes.
Verified on Windows 11 + WSL2 (Ubuntu, mirrored): a root-owned holder
with cwd in the install dir that the old code left untouched is now
detected and SIGTERM-cleared before the installer handoff.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@SunkenInTimeChatGPT Codex Connector

Copy link
Copy Markdown
Contributor

Current head 249a6d93da1c094c9fe2f94af989a6377dcbe6cb is not merge-ready because the required Check job is red on formatting.

The failing job reports formatting issues in:

  • DesktopInstallIntegrity.ts
  • DesktopBackendConfiguration.test.ts / .ts
  • DesktopBackendManager.test.ts / .ts
  • DesktopUpdates.test.ts / .ts

Please run the repository formatter and push the result. I reproduced the functional focused suite on Windows at 126/127 passing; the one failure is the pre-existing Windows slash assertion in the external resource-monitor test, and both desktop and script-scope typechecks pass.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

2 participants

@Jgratton24@SunkenInTime
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(desktop): verified update teardown on Windows+WSL, install integrity check, and resilient WSL readiness handshake - #5998

Open
Jgratton24 wants to merge 9 commits into
pingdotgg:mainfrom
Jgratton24:fix/update-apply-wsl-handshake
Open

fix(desktop): verified update teardown on Windows+WSL, install integrity check, and resilient WSL readiness handshake#5998
Jgratton24 wants to merge 9 commits into
pingdotgg:mainfrom
Jgratton24:fix/update-apply-wsl-handshake

Conversation

@Jgratton24

@Jgratton24Jgratton24 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What Changed

Two Windows+WSL reliability fixes that share one user-visible symptom (an indefinite "Connecting to WSL…" splash), plus a guard that turns the corrupted-install crash into an actionable dialog. Desktop-only; no apps/server changes.

1. Update apply is verified before the installer runs (fixes the half-applied-update corruption).

  • DesktopBackendInstance.stop() now reports whether the child process actually finalized within the timeout instead of silently swallowing it (DesktopBackendManager.ts).
  • The install path (DesktopUpdates.ts) aborts — restarting backends and surfacing the error through the update state machine — if any backend can't be verified stopped, instead of handing off to NSIS while a process still holds files under the install dir.
  • For WSL backends, a stopped wsl.exe relay does not mean the Linux-side server (or helpers it spawned, e.g. cloudflared) exited with it. New ensureWindowsPathReleased (DesktopWslEnvironment.ts) terminates and verifies from inside the distro that no Linux process still holds cwd/exe/fd/mmap references under /mnt/c/<install dir> — 9p/DrvFs handles held from WSL block file replacement on the Windows side, which is how app.asar and app.asar.unpacked end up from different builds.
  • Post-apply integrity check: the build stamps apps/server/dist/desktop-build-manifest.json (under app.asar.unpacked on Windows); startup compares it against the asar's version and refuses to launch a mixed-build install with a clear repair dialog (DesktopInstallIntegrity.ts) instead of the opaque ERR_MODULE_NOT_FOUND main-process crash. Missing manifest (dev, macOS/Linux, pre-stamp installs) skips the check, so no false positives.

2. WSL endpoint detection and the readiness handshake are no longer single-point-of-failure.

  • Networking mode now comes from wslinfo --networking-mode (first-party, present on WSL 2.6.x and 2.7.x) instead of being inferred from the first address in hostname -I — whose ordering shifts whenever Tailscale, Docker bridges, or VPN adapters exist inside the distro. Heuristic fallback for older WSL matches any distro IP against Windows interfaces, never position one, and CGNAT 100.64/10 addresses are never advertised.
  • Readiness probes race loopback plus every enumerated distro address; the first candidate that answers wins and becomes the advertised base URL — so a wrong guess degrades startup by nothing worse than a probe race.
  • After all static candidates time out, the backend's persisted server-runtime.json origin is consulted as a last-resort candidate (gated on it naming this instance's port).
  • The readiness probe is no longer one-shot: a run that spawns but never answers is terminated and retried, and after 3 readiness timeouts the failure surfaces like an exhausted preflight (dialog + Windows fallback on the primary, inline Connections error on the secondary) with every probed URL named. Resolved mode and candidates are logged at startup.

25 new tests across DesktopBackendManager, DesktopUpdates, DesktopWslEnvironment, DesktopBackendConfiguration, and DesktopInstallIntegrity cover the regressions above (CGNAT-first ordering, docker0-first, wslinfo absent, one-shot-probe limbo, unverified stop, WSL busy at install time, mixed-build detection). Full desktop suite: 473 passing.

Why

Two field incidents on 0.0.33-nightly builds, plus these open issues:

The half-applied update we debugged: an in-app update left app.asar + the exe at the old build while app.asar.unpacked/node_modules was new → ERR_MODULE_NOT_FOUND on every launch, with the updater having reported nothing. Root cause: install teardown killed the wsl.exe relay and proceeded on a 5s timeout that was silently swallowed, while the Linux-side server exited asynchronously still holding 9p handles into the install dir. Same version installed fine with the app closed.

Sizing note: aware of the contribution guidelines — roughly half of this diff is tests, and the two fixes share the same touched files (DesktopBackendManager/DesktopWslEnvironment), which is why they're one PR. Happy to split into updater-atomicity and handshake PRs if you'd prefer.

This PR deliberately avoids every file touched by #2829 (orchestrator v2) — verified against its full file list, so it merges cleanly before or after.

UI Changes

No renderer UI changes. Two native dialogs are added/extended (update-install-aborted error via the existing update state machine; "installation is damaged" message box with an Open Download Page button). Not screenshotted — they require a packaged Windows build to trigger, which this dev environment can't produce; dialog copy is in DesktopInstallIntegrity.ts and DesktopUpdates.ts.

Checklist

  • This PR is small and focused (two related reliability fixes, no feature work; ~half the diff is tests)
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (native dialogs only; see UI Changes)
  • I included a video for animation/interaction changes (n/a)

🤖 Generated with Claude Code


Note

High Risk
Changes touch critical Windows update apply, WSL process killing (root), and startup abort paths; incorrect readiness or release checks could block launches or updates, though behavior is heavily tested.

Overview
This PR targets two reliability problems on Windows with WSL—indefinite “connecting” and half-applied updates—and adds a startup guard for corrupted installs.

WSL backend reachability no longer trusts the first hostname -I address. Networking mode comes from wslinfo --networking-mode (with heuristics when unknown), CGNAT 100.64/10 IPs are never advertised, and readiness races loopback plus every distro IP; the winning URL is rebound into currentConfig. After static probes time out, server-runtime.json supplies fallback candidates. Runs that never become ready are terminated and retried, with caps that surface failures via onPreflightFailed instead of hanging forever. getDistroIp is replaced by getDistroIps.

Updates require each backend stop() to return whether the child actually exited within a 10s budget; failed installs abort, restart backends, and leave the update downloadable. On Windows, ensureWindowsPathReleased verifies WSL has released the install directory (released / busy / unknown) before quitAndInstall.

Install integrity compares the packaged app version to desktop-build-manifest.json under app.asar.unpacked (stamped at build time); mismatches or a missing manifest on packaged Windows show a repair dialog and exit before bootstrap. The build script writes the manifest into staged apps/server/dist.

Reviewed by Cursor Bugbot for commit 249a6d9. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix update teardown on Windows+WSL with install integrity checks and resilient WSL readiness handshake

  • Startup now runs an install-integrity check via DesktopInstallIntegrity before bootstrapping; on Windows, a desktop-build-manifest.json stamped at build time is compared against the app version and, on mismatch, shows an error dialog and quits.
  • The update install flow in DesktopUpdates.ts now requires each running backend to confirm a finalized stop within 10 seconds and, on Windows+WSL, calls ensureWindowsPathReleased to verify the install directory is free before invoking quitAndInstall; on failure it restarts backends and surfaces an error.
  • WSL readiness probing in DesktopBackendManager.ts now races multiple candidate URLs (loopback + all distro IPs) and optionally consults persisted runtime-state fallback URLs before timing out; stop() now returns a boolean indicating whether the process finalized before the timeout.
  • DesktopWslEnvironment.ts replaces the single-IP getDistroIp with getDistroIps (all IPv4s), and adds getNetworkingMode, readServerRuntimeState, and ensureWindowsPathReleased with fixed timeouts and fallback semantics.
  • Risk: backends that do not finalize within 10 seconds during an update will block the installer and restart, changing prior best-effort stop behavior.

Macroscope summarized 249a6d9.

…iness handshake
Two user-facing failures shared one splash-hang symptom; both are fixed here.
Update apply (half-applied install):
- instance.stop() now reports whether the child fully finalized; the
install path aborts (and restarts backends, surfacing the error through
the update state machine) instead of running NSIS while a backend that
holds install-dir handles may still be alive.
- Before handing off to the installer on Windows, verify from inside the
distro that no Linux process still holds cwd/exe/fd/mmap references
under /mnt/c/<install dir> (ensureWindowsPathReleased: TERM, then KILL,
then report busy). 9p handles held from WSL block file replacement on
the Windows side, which is how app.asar and app.asar.unpacked ended up
from different builds.
- Post-apply integrity check: the build stamps
apps/server/dist/desktop-build-manifest.json (asar-unpacked on Windows);
startup compares it against the asar version and refuses to launch a
half-applied install with an actionable repair dialog instead of an
opaque ERR_MODULE_NOT_FOUND crash.
WSL readiness handshake (indefinite 'Connecting to WSL'):
- Networking mode now comes from wslinfo --networking-mode instead of
inferring it from the FIRST hostname -I address (a WSL-side tailscaled
putting a CGNAT 100.64/10 address first made mirrored hosts look NAT'd
and pointed every probe at an unroutable IP).
- Readiness races loopback and every enumerated distro address; the first
candidate that answers wins and becomes the advertised base URL.
- After all static candidates time out, the server's persisted
server-runtime.json origin is consulted as a last-resort candidate.
- A run that spawns but never answers any probe is terminated and retried;
after 3 readiness timeouts the failure surfaces like an exhausted
preflight (dialog + Windows fallback on the primary, inline error on the
secondary) with the probed URLs named, instead of sitting on the splash
forever. Resolved mode and candidates are logged at startup.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Aug 10, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e310a9a5-be8d-496e-b0cb-2823e06b141c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 10, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect service conventions review: one convention violation found — the new readiness fallback path uses Effect.catchTag, which the conventions replace with Effect.catchTags({ ... }) even when recovering a single tag. Everything else (subpath namespace imports, service-member additions on DesktopWslEnvironment with matching layerTest stubs, environment-based dependency acquisition in DesktopUpdates.make, Schema.TaggedErrorClass attributes with an attribute-derived message) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f49d4ea5f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/desktop/src/app/DesktopInstallIntegrity.ts Outdated
Comment threadapps/desktop/src/updates/DesktopUpdates.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
@macroscopeapp

macroscopeappBot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces significant new runtime behavior beyond a simple fix: a startup integrity check that can block app launch, fundamental changes to WSL backend connection logic with multi-candidate probing, new update install abort conditions when backends don't fully stop, and new capabilities to kill Linux processes holding files. The ~2200 additions span multiple subsystems and warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Jgratton24and others added 2 commits August 10, 2026 08:34
- Recover known tags with Effect.catchTags (convention) in the readiness
fallback path.
- Anchor the install-dir holder scan: descendants match "$dir/" and the
directory itself matches as an exact readlink line, so /mnt/c/application
is no longer classified as a holder of /mnt/c/app.
- Split the release-check result: "unavailable" (wsl.exe would not spawn —
no running VM can hold /mnt handles) proceeds with a warning, while
"unknown" (timeout/path-translation failure against a distro that was
hosting this backend moments ago) now aborts the install.
- abortInstall restarts only instances that were running before teardown,
so a previously-stopped backend is not resurrected by a failed attempt.
- readServerRuntimeState reads the state-dir flavor the spawned backend
actually uses (dev/ for --dev-url runs, userdata/ for packaged) instead
of preferring a possibly-stale packaged file.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ess surfacing
- A packaged Windows build with NO unpacked build manifest is now treated
as a half-applied update (new app.asar over a pre-stamp unpacked tree)
instead of skipping the check — every Windows artifact that ships the
checker also ships the stamp. Platforms that pack the server dist inside
the asar still skip.
- A non-fatal surfacing on a primary that is already the Windows backend
(only reachable via readiness exhaustion) now shows one dialog and stops
instead of applying a no-op WSL fallback and looping dialog + restart
forever.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadapps/desktop/src/app/DesktopInstallIntegrity.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Jgratton24and others added 2 commits August 10, 2026 08:48
…WSL release always aborts
- The integrity check now distinguishes read failures: only a NotFound on
packaged Windows is treated as a half-applied update. An unreadable
manifest (access denied, I/O error) logs and skips instead of falsely
bricking a healthy install.
- Drop the "unavailable" proceed path from the WSL release check: a
release-script spawn failure happens after wslpath already succeeded
against the distro, so it cannot prove WSL is gone — every unverified
outcome now reads "unknown" and aborts the install. A machine whose WSL
layer is genuinely broken recovers on the next launch via the preflight
Windows fallback (no WSL config in the pool means no release check).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nstead of restarting forever
Ports the still-relevant piece of pingdotgg#3623: a backend that spawns fine but
keeps exiting before it ever answers a readiness probe previously
restarted silently forever (500ms-10s backoff, no cap, no surfacing).
After 5 consecutive never-ready exits the failure now surfaces through
onPreflightFailed — dialog + Windows fallback on the primary, inline
Connections error on the WSL secondary — exactly like exhausted preflight
and readiness failures.
The counter is scoped strictly to post-spawn exits: pre-spawn preflight
retries have their own counter, a crash after the backend was ready
resets the streak (an established backend crashing is a different failure
than one that never comes up), and the budget refreshes on ready, on
external stop, and on a fresh manual start so one cap firing does not
permanently remove the retry budget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 10, 2026
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 02343e8. Configure here.

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
…r-ready exit cap
A readiness-timeout termination closes the run scope, and the resulting
child exit previously ALSO incremented exitFailureAttempt — every
readiness timeout double-counted as a crash, so after a readiness-cap
recovery the leftover streak could fire a second, misleading "exited N
times" dialog just two real failures later. The readiness kill now marks
the run stopRequested before closing its scope, which both excludes it
from the exit cap and routes finalize to discardSession (the failure log
was already captured via persistFailureSnapshot). Regression test drives
five readiness cycles with a retrying onPreflightFailed and asserts the
exit cap never fires.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Jgratton24
Jgratton24force-pushed the fix/update-apply-wsl-handshake branch from 8e4c156 to 61c09f0CompareAugust 10, 2026 13:54
Comment threadapps/desktop/src/backend/DesktopBackendPool.ts Outdated
Jgratton24and others added 2 commits August 10, 2026 10:06
…SL-primary condition
resolvePrimary only selects the WSL backend when BOTH wslOnly and
wslBackendEnabled are set (wslRequested), but the non-fatal surfacing
branch checked wslOnly alone. With wslOnly persisted true while
wslBackendEnabled is false, a Windows primary exhausting readiness
retries took the WSL branch: a misleading "WSL backend is still
unavailable" dialog, an unrelated in-memory WSL settings mutation, and
one wasted retry cycle before the stop branch finally fired. The guard
now mirrors resolvePrimary's own condition, so a Windows primary
surfaces the generic backend-unavailable dialog and stops immediately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live testing on a real Windows+WSL host (WSL 2.7, mirrored) showed that
plain Linux fds and mmaps through 9p/DrvFs do NOT block Windows-side file
replacement, while a Windows-side handle does (the backend child runs as
the app executable and reads through app.asar). Comments now describe the
WSL release check as the defensive guarantee it is, with the verified stop
of Windows backend children as the confirmed-mechanism fix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ensureWindowsPathReleased spawned the holder scan as the default WSL
user. find_holders inspects /proc/<pid>/{cwd,exe,fd,maps}, which the
kernel exposes only to the process owner and root, so the unprivileged
scan silently got EACCES for any process it did not own. A root- or
other-user-owned process holding the install directory was therefore
invisible: the check returned RELEASED without ever seeing (let alone
killing) it, defeating the guard in exactly the case it exists for.
Spawn the probe as `wsl -u root` (WSL grants root with no password,
gated by the Windows host) so the scan sees every holder and its kills
land on holders it does not own. find_holders is already scoped to
processes that reference the install dir, so this cannot reach
unrelated processes.
Verified on Windows 11 + WSL2 (Ubuntu, mirrored): a root-owned holder
with cwd in the install dir that the old code left untouched is now
detected and SIGTERM-cleared before the installer handoff.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@SunkenInTimeChatGPT Codex Connector

Copy link
Copy Markdown
Contributor

Current head 249a6d93da1c094c9fe2f94af989a6377dcbe6cb is not merge-ready because the required Check job is red on formatting.

The failing job reports formatting issues in:

  • DesktopInstallIntegrity.ts
  • DesktopBackendConfiguration.test.ts / .ts
  • DesktopBackendManager.test.ts / .ts
  • DesktopUpdates.test.ts / .ts

Please run the repository formatter and push the result. I reproduced the functional focused suite on Windows at 126/127 passing; the one failure is the pre-existing Windows slash assertion in the external resource-monitor test, and both desktop and script-scope typechecks pass.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

2 participants

@Jgratton24@SunkenInTime
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(desktop): verified update teardown on Windows+WSL, install integrity check, and resilient WSL readiness handshake - #5998

Open
Jgratton24 wants to merge 9 commits into
pingdotgg:mainfrom
Jgratton24:fix/update-apply-wsl-handshake
Open

fix(desktop): verified update teardown on Windows+WSL, install integrity check, and resilient WSL readiness handshake#5998
Jgratton24 wants to merge 9 commits into
pingdotgg:mainfrom
Jgratton24:fix/update-apply-wsl-handshake

Conversation

@Jgratton24

@Jgratton24Jgratton24 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What Changed

Two Windows+WSL reliability fixes that share one user-visible symptom (an indefinite "Connecting to WSL…" splash), plus a guard that turns the corrupted-install crash into an actionable dialog. Desktop-only; no apps/server changes.

1. Update apply is verified before the installer runs (fixes the half-applied-update corruption).

  • DesktopBackendInstance.stop() now reports whether the child process actually finalized within the timeout instead of silently swallowing it (DesktopBackendManager.ts).
  • The install path (DesktopUpdates.ts) aborts — restarting backends and surfacing the error through the update state machine — if any backend can't be verified stopped, instead of handing off to NSIS while a process still holds files under the install dir.
  • For WSL backends, a stopped wsl.exe relay does not mean the Linux-side server (or helpers it spawned, e.g. cloudflared) exited with it. New ensureWindowsPathReleased (DesktopWslEnvironment.ts) terminates and verifies from inside the distro that no Linux process still holds cwd/exe/fd/mmap references under /mnt/c/<install dir> — 9p/DrvFs handles held from WSL block file replacement on the Windows side, which is how app.asar and app.asar.unpacked end up from different builds.
  • Post-apply integrity check: the build stamps apps/server/dist/desktop-build-manifest.json (under app.asar.unpacked on Windows); startup compares it against the asar's version and refuses to launch a mixed-build install with a clear repair dialog (DesktopInstallIntegrity.ts) instead of the opaque ERR_MODULE_NOT_FOUND main-process crash. Missing manifest (dev, macOS/Linux, pre-stamp installs) skips the check, so no false positives.

2. WSL endpoint detection and the readiness handshake are no longer single-point-of-failure.

  • Networking mode now comes from wslinfo --networking-mode (first-party, present on WSL 2.6.x and 2.7.x) instead of being inferred from the first address in hostname -I — whose ordering shifts whenever Tailscale, Docker bridges, or VPN adapters exist inside the distro. Heuristic fallback for older WSL matches any distro IP against Windows interfaces, never position one, and CGNAT 100.64/10 addresses are never advertised.
  • Readiness probes race loopback plus every enumerated distro address; the first candidate that answers wins and becomes the advertised base URL — so a wrong guess degrades startup by nothing worse than a probe race.
  • After all static candidates time out, the backend's persisted server-runtime.json origin is consulted as a last-resort candidate (gated on it naming this instance's port).
  • The readiness probe is no longer one-shot: a run that spawns but never answers is terminated and retried, and after 3 readiness timeouts the failure surfaces like an exhausted preflight (dialog + Windows fallback on the primary, inline Connections error on the secondary) with every probed URL named. Resolved mode and candidates are logged at startup.

25 new tests across DesktopBackendManager, DesktopUpdates, DesktopWslEnvironment, DesktopBackendConfiguration, and DesktopInstallIntegrity cover the regressions above (CGNAT-first ordering, docker0-first, wslinfo absent, one-shot-probe limbo, unverified stop, WSL busy at install time, mixed-build detection). Full desktop suite: 473 passing.

Why

Two field incidents on 0.0.33-nightly builds, plus these open issues:

The half-applied update we debugged: an in-app update left app.asar + the exe at the old build while app.asar.unpacked/node_modules was new → ERR_MODULE_NOT_FOUND on every launch, with the updater having reported nothing. Root cause: install teardown killed the wsl.exe relay and proceeded on a 5s timeout that was silently swallowed, while the Linux-side server exited asynchronously still holding 9p handles into the install dir. Same version installed fine with the app closed.

Sizing note: aware of the contribution guidelines — roughly half of this diff is tests, and the two fixes share the same touched files (DesktopBackendManager/DesktopWslEnvironment), which is why they're one PR. Happy to split into updater-atomicity and handshake PRs if you'd prefer.

This PR deliberately avoids every file touched by #2829 (orchestrator v2) — verified against its full file list, so it merges cleanly before or after.

UI Changes

No renderer UI changes. Two native dialogs are added/extended (update-install-aborted error via the existing update state machine; "installation is damaged" message box with an Open Download Page button). Not screenshotted — they require a packaged Windows build to trigger, which this dev environment can't produce; dialog copy is in DesktopInstallIntegrity.ts and DesktopUpdates.ts.

Checklist

  • This PR is small and focused (two related reliability fixes, no feature work; ~half the diff is tests)
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (native dialogs only; see UI Changes)
  • I included a video for animation/interaction changes (n/a)

🤖 Generated with Claude Code


Note

High Risk
Changes touch critical Windows update apply, WSL process killing (root), and startup abort paths; incorrect readiness or release checks could block launches or updates, though behavior is heavily tested.

Overview
This PR targets two reliability problems on Windows with WSL—indefinite “connecting” and half-applied updates—and adds a startup guard for corrupted installs.

WSL backend reachability no longer trusts the first hostname -I address. Networking mode comes from wslinfo --networking-mode (with heuristics when unknown), CGNAT 100.64/10 IPs are never advertised, and readiness races loopback plus every distro IP; the winning URL is rebound into currentConfig. After static probes time out, server-runtime.json supplies fallback candidates. Runs that never become ready are terminated and retried, with caps that surface failures via onPreflightFailed instead of hanging forever. getDistroIp is replaced by getDistroIps.

Updates require each backend stop() to return whether the child actually exited within a 10s budget; failed installs abort, restart backends, and leave the update downloadable. On Windows, ensureWindowsPathReleased verifies WSL has released the install directory (released / busy / unknown) before quitAndInstall.

Install integrity compares the packaged app version to desktop-build-manifest.json under app.asar.unpacked (stamped at build time); mismatches or a missing manifest on packaged Windows show a repair dialog and exit before bootstrap. The build script writes the manifest into staged apps/server/dist.

Reviewed by Cursor Bugbot for commit 249a6d9. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix update teardown on Windows+WSL with install integrity checks and resilient WSL readiness handshake

  • Startup now runs an install-integrity check via DesktopInstallIntegrity before bootstrapping; on Windows, a desktop-build-manifest.json stamped at build time is compared against the app version and, on mismatch, shows an error dialog and quits.
  • The update install flow in DesktopUpdates.ts now requires each running backend to confirm a finalized stop within 10 seconds and, on Windows+WSL, calls ensureWindowsPathReleased to verify the install directory is free before invoking quitAndInstall; on failure it restarts backends and surfaces an error.
  • WSL readiness probing in DesktopBackendManager.ts now races multiple candidate URLs (loopback + all distro IPs) and optionally consults persisted runtime-state fallback URLs before timing out; stop() now returns a boolean indicating whether the process finalized before the timeout.
  • DesktopWslEnvironment.ts replaces the single-IP getDistroIp with getDistroIps (all IPv4s), and adds getNetworkingMode, readServerRuntimeState, and ensureWindowsPathReleased with fixed timeouts and fallback semantics.
  • Risk: backends that do not finalize within 10 seconds during an update will block the installer and restart, changing prior best-effort stop behavior.

Macroscope summarized 249a6d9.

…iness handshake
Two user-facing failures shared one splash-hang symptom; both are fixed here.
Update apply (half-applied install):
- instance.stop() now reports whether the child fully finalized; the
install path aborts (and restarts backends, surfacing the error through
the update state machine) instead of running NSIS while a backend that
holds install-dir handles may still be alive.
- Before handing off to the installer on Windows, verify from inside the
distro that no Linux process still holds cwd/exe/fd/mmap references
under /mnt/c/<install dir> (ensureWindowsPathReleased: TERM, then KILL,
then report busy). 9p handles held from WSL block file replacement on
the Windows side, which is how app.asar and app.asar.unpacked ended up
from different builds.
- Post-apply integrity check: the build stamps
apps/server/dist/desktop-build-manifest.json (asar-unpacked on Windows);
startup compares it against the asar version and refuses to launch a
half-applied install with an actionable repair dialog instead of an
opaque ERR_MODULE_NOT_FOUND crash.
WSL readiness handshake (indefinite 'Connecting to WSL'):
- Networking mode now comes from wslinfo --networking-mode instead of
inferring it from the FIRST hostname -I address (a WSL-side tailscaled
putting a CGNAT 100.64/10 address first made mirrored hosts look NAT'd
and pointed every probe at an unroutable IP).
- Readiness races loopback and every enumerated distro address; the first
candidate that answers wins and becomes the advertised base URL.
- After all static candidates time out, the server's persisted
server-runtime.json origin is consulted as a last-resort candidate.
- A run that spawns but never answers any probe is terminated and retried;
after 3 readiness timeouts the failure surfaces like an exhausted
preflight (dialog + Windows fallback on the primary, inline error on the
secondary) with the probed URLs named, instead of sitting on the splash
forever. Resolved mode and candidates are logged at startup.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Aug 10, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e310a9a5-be8d-496e-b0cb-2823e06b141c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 10, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect service conventions review: one convention violation found — the new readiness fallback path uses Effect.catchTag, which the conventions replace with Effect.catchTags({ ... }) even when recovering a single tag. Everything else (subpath namespace imports, service-member additions on DesktopWslEnvironment with matching layerTest stubs, environment-based dependency acquisition in DesktopUpdates.make, Schema.TaggedErrorClass attributes with an attribute-derived message) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f49d4ea5f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/desktop/src/app/DesktopInstallIntegrity.ts Outdated
Comment threadapps/desktop/src/updates/DesktopUpdates.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
@macroscopeapp

macroscopeappBot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces significant new runtime behavior beyond a simple fix: a startup integrity check that can block app launch, fundamental changes to WSL backend connection logic with multi-candidate probing, new update install abort conditions when backends don't fully stop, and new capabilities to kill Linux processes holding files. The ~2200 additions span multiple subsystems and warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Jgratton24and others added 2 commits August 10, 2026 08:34
- Recover known tags with Effect.catchTags (convention) in the readiness
fallback path.
- Anchor the install-dir holder scan: descendants match "$dir/" and the
directory itself matches as an exact readlink line, so /mnt/c/application
is no longer classified as a holder of /mnt/c/app.
- Split the release-check result: "unavailable" (wsl.exe would not spawn —
no running VM can hold /mnt handles) proceeds with a warning, while
"unknown" (timeout/path-translation failure against a distro that was
hosting this backend moments ago) now aborts the install.
- abortInstall restarts only instances that were running before teardown,
so a previously-stopped backend is not resurrected by a failed attempt.
- readServerRuntimeState reads the state-dir flavor the spawned backend
actually uses (dev/ for --dev-url runs, userdata/ for packaged) instead
of preferring a possibly-stale packaged file.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ess surfacing
- A packaged Windows build with NO unpacked build manifest is now treated
as a half-applied update (new app.asar over a pre-stamp unpacked tree)
instead of skipping the check — every Windows artifact that ships the
checker also ships the stamp. Platforms that pack the server dist inside
the asar still skip.
- A non-fatal surfacing on a primary that is already the Windows backend
(only reachable via readiness exhaustion) now shows one dialog and stops
instead of applying a no-op WSL fallback and looping dialog + restart
forever.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadapps/desktop/src/app/DesktopInstallIntegrity.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Jgratton24and others added 2 commits August 10, 2026 08:48
…WSL release always aborts
- The integrity check now distinguishes read failures: only a NotFound on
packaged Windows is treated as a half-applied update. An unreadable
manifest (access denied, I/O error) logs and skips instead of falsely
bricking a healthy install.
- Drop the "unavailable" proceed path from the WSL release check: a
release-script spawn failure happens after wslpath already succeeded
against the distro, so it cannot prove WSL is gone — every unverified
outcome now reads "unknown" and aborts the install. A machine whose WSL
layer is genuinely broken recovers on the next launch via the preflight
Windows fallback (no WSL config in the pool means no release check).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nstead of restarting forever
Ports the still-relevant piece of pingdotgg#3623: a backend that spawns fine but
keeps exiting before it ever answers a readiness probe previously
restarted silently forever (500ms-10s backoff, no cap, no surfacing).
After 5 consecutive never-ready exits the failure now surfaces through
onPreflightFailed — dialog + Windows fallback on the primary, inline
Connections error on the WSL secondary — exactly like exhausted preflight
and readiness failures.
The counter is scoped strictly to post-spawn exits: pre-spawn preflight
retries have their own counter, a crash after the backend was ready
resets the streak (an established backend crashing is a different failure
than one that never comes up), and the budget refreshes on ready, on
external stop, and on a fresh manual start so one cap firing does not
permanently remove the retry budget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 10, 2026
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 02343e8. Configure here.

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
…r-ready exit cap
A readiness-timeout termination closes the run scope, and the resulting
child exit previously ALSO incremented exitFailureAttempt — every
readiness timeout double-counted as a crash, so after a readiness-cap
recovery the leftover streak could fire a second, misleading "exited N
times" dialog just two real failures later. The readiness kill now marks
the run stopRequested before closing its scope, which both excludes it
from the exit cap and routes finalize to discardSession (the failure log
was already captured via persistFailureSnapshot). Regression test drives
five readiness cycles with a retrying onPreflightFailed and asserts the
exit cap never fires.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Jgratton24
Jgratton24force-pushed the fix/update-apply-wsl-handshake branch from 8e4c156 to 61c09f0CompareAugust 10, 2026 13:54
Comment threadapps/desktop/src/backend/DesktopBackendPool.ts Outdated
Jgratton24and others added 2 commits August 10, 2026 10:06
…SL-primary condition
resolvePrimary only selects the WSL backend when BOTH wslOnly and
wslBackendEnabled are set (wslRequested), but the non-fatal surfacing
branch checked wslOnly alone. With wslOnly persisted true while
wslBackendEnabled is false, a Windows primary exhausting readiness
retries took the WSL branch: a misleading "WSL backend is still
unavailable" dialog, an unrelated in-memory WSL settings mutation, and
one wasted retry cycle before the stop branch finally fired. The guard
now mirrors resolvePrimary's own condition, so a Windows primary
surfaces the generic backend-unavailable dialog and stops immediately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live testing on a real Windows+WSL host (WSL 2.7, mirrored) showed that
plain Linux fds and mmaps through 9p/DrvFs do NOT block Windows-side file
replacement, while a Windows-side handle does (the backend child runs as
the app executable and reads through app.asar). Comments now describe the
WSL release check as the defensive guarantee it is, with the verified stop
of Windows backend children as the confirmed-mechanism fix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ensureWindowsPathReleased spawned the holder scan as the default WSL
user. find_holders inspects /proc/<pid>/{cwd,exe,fd,maps}, which the
kernel exposes only to the process owner and root, so the unprivileged
scan silently got EACCES for any process it did not own. A root- or
other-user-owned process holding the install directory was therefore
invisible: the check returned RELEASED without ever seeing (let alone
killing) it, defeating the guard in exactly the case it exists for.
Spawn the probe as `wsl -u root` (WSL grants root with no password,
gated by the Windows host) so the scan sees every holder and its kills
land on holders it does not own. find_holders is already scoped to
processes that reference the install dir, so this cannot reach
unrelated processes.
Verified on Windows 11 + WSL2 (Ubuntu, mirrored): a root-owned holder
with cwd in the install dir that the old code left untouched is now
detected and SIGTERM-cleared before the installer handoff.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@SunkenInTimeChatGPT Codex Connector

Copy link
Copy Markdown
Contributor

Current head 249a6d93da1c094c9fe2f94af989a6377dcbe6cb is not merge-ready because the required Check job is red on formatting.

The failing job reports formatting issues in:

  • DesktopInstallIntegrity.ts
  • DesktopBackendConfiguration.test.ts / .ts
  • DesktopBackendManager.test.ts / .ts
  • DesktopUpdates.test.ts / .ts

Please run the repository formatter and push the result. I reproduced the functional focused suite on Windows at 126/127 passing; the one failure is the pre-existing Windows slash assertion in the external resource-monitor test, and both desktop and script-scope typechecks pass.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

2 participants

@Jgratton24@SunkenInTime
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(desktop): verified update teardown on Windows+WSL, install integrity check, and resilient WSL readiness handshake - #5998

Open
Jgratton24 wants to merge 9 commits into
pingdotgg:mainfrom
Jgratton24:fix/update-apply-wsl-handshake
Open

fix(desktop): verified update teardown on Windows+WSL, install integrity check, and resilient WSL readiness handshake#5998
Jgratton24 wants to merge 9 commits into
pingdotgg:mainfrom
Jgratton24:fix/update-apply-wsl-handshake

Conversation

@Jgratton24

@Jgratton24Jgratton24 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What Changed

Two Windows+WSL reliability fixes that share one user-visible symptom (an indefinite "Connecting to WSL…" splash), plus a guard that turns the corrupted-install crash into an actionable dialog. Desktop-only; no apps/server changes.

1. Update apply is verified before the installer runs (fixes the half-applied-update corruption).

  • DesktopBackendInstance.stop() now reports whether the child process actually finalized within the timeout instead of silently swallowing it (DesktopBackendManager.ts).
  • The install path (DesktopUpdates.ts) aborts — restarting backends and surfacing the error through the update state machine — if any backend can't be verified stopped, instead of handing off to NSIS while a process still holds files under the install dir.
  • For WSL backends, a stopped wsl.exe relay does not mean the Linux-side server (or helpers it spawned, e.g. cloudflared) exited with it. New ensureWindowsPathReleased (DesktopWslEnvironment.ts) terminates and verifies from inside the distro that no Linux process still holds cwd/exe/fd/mmap references under /mnt/c/<install dir> — 9p/DrvFs handles held from WSL block file replacement on the Windows side, which is how app.asar and app.asar.unpacked end up from different builds.
  • Post-apply integrity check: the build stamps apps/server/dist/desktop-build-manifest.json (under app.asar.unpacked on Windows); startup compares it against the asar's version and refuses to launch a mixed-build install with a clear repair dialog (DesktopInstallIntegrity.ts) instead of the opaque ERR_MODULE_NOT_FOUND main-process crash. Missing manifest (dev, macOS/Linux, pre-stamp installs) skips the check, so no false positives.

2. WSL endpoint detection and the readiness handshake are no longer single-point-of-failure.

  • Networking mode now comes from wslinfo --networking-mode (first-party, present on WSL 2.6.x and 2.7.x) instead of being inferred from the first address in hostname -I — whose ordering shifts whenever Tailscale, Docker bridges, or VPN adapters exist inside the distro. Heuristic fallback for older WSL matches any distro IP against Windows interfaces, never position one, and CGNAT 100.64/10 addresses are never advertised.
  • Readiness probes race loopback plus every enumerated distro address; the first candidate that answers wins and becomes the advertised base URL — so a wrong guess degrades startup by nothing worse than a probe race.
  • After all static candidates time out, the backend's persisted server-runtime.json origin is consulted as a last-resort candidate (gated on it naming this instance's port).
  • The readiness probe is no longer one-shot: a run that spawns but never answers is terminated and retried, and after 3 readiness timeouts the failure surfaces like an exhausted preflight (dialog + Windows fallback on the primary, inline Connections error on the secondary) with every probed URL named. Resolved mode and candidates are logged at startup.

25 new tests across DesktopBackendManager, DesktopUpdates, DesktopWslEnvironment, DesktopBackendConfiguration, and DesktopInstallIntegrity cover the regressions above (CGNAT-first ordering, docker0-first, wslinfo absent, one-shot-probe limbo, unverified stop, WSL busy at install time, mixed-build detection). Full desktop suite: 473 passing.

Why

Two field incidents on 0.0.33-nightly builds, plus these open issues:

The half-applied update we debugged: an in-app update left app.asar + the exe at the old build while app.asar.unpacked/node_modules was new → ERR_MODULE_NOT_FOUND on every launch, with the updater having reported nothing. Root cause: install teardown killed the wsl.exe relay and proceeded on a 5s timeout that was silently swallowed, while the Linux-side server exited asynchronously still holding 9p handles into the install dir. Same version installed fine with the app closed.

Sizing note: aware of the contribution guidelines — roughly half of this diff is tests, and the two fixes share the same touched files (DesktopBackendManager/DesktopWslEnvironment), which is why they're one PR. Happy to split into updater-atomicity and handshake PRs if you'd prefer.

This PR deliberately avoids every file touched by #2829 (orchestrator v2) — verified against its full file list, so it merges cleanly before or after.

UI Changes

No renderer UI changes. Two native dialogs are added/extended (update-install-aborted error via the existing update state machine; "installation is damaged" message box with an Open Download Page button). Not screenshotted — they require a packaged Windows build to trigger, which this dev environment can't produce; dialog copy is in DesktopInstallIntegrity.ts and DesktopUpdates.ts.

Checklist

  • This PR is small and focused (two related reliability fixes, no feature work; ~half the diff is tests)
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (native dialogs only; see UI Changes)
  • I included a video for animation/interaction changes (n/a)

🤖 Generated with Claude Code


Note

High Risk
Changes touch critical Windows update apply, WSL process killing (root), and startup abort paths; incorrect readiness or release checks could block launches or updates, though behavior is heavily tested.

Overview
This PR targets two reliability problems on Windows with WSL—indefinite “connecting” and half-applied updates—and adds a startup guard for corrupted installs.

WSL backend reachability no longer trusts the first hostname -I address. Networking mode comes from wslinfo --networking-mode (with heuristics when unknown), CGNAT 100.64/10 IPs are never advertised, and readiness races loopback plus every distro IP; the winning URL is rebound into currentConfig. After static probes time out, server-runtime.json supplies fallback candidates. Runs that never become ready are terminated and retried, with caps that surface failures via onPreflightFailed instead of hanging forever. getDistroIp is replaced by getDistroIps.

Updates require each backend stop() to return whether the child actually exited within a 10s budget; failed installs abort, restart backends, and leave the update downloadable. On Windows, ensureWindowsPathReleased verifies WSL has released the install directory (released / busy / unknown) before quitAndInstall.

Install integrity compares the packaged app version to desktop-build-manifest.json under app.asar.unpacked (stamped at build time); mismatches or a missing manifest on packaged Windows show a repair dialog and exit before bootstrap. The build script writes the manifest into staged apps/server/dist.

Reviewed by Cursor Bugbot for commit 249a6d9. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix update teardown on Windows+WSL with install integrity checks and resilient WSL readiness handshake

  • Startup now runs an install-integrity check via DesktopInstallIntegrity before bootstrapping; on Windows, a desktop-build-manifest.json stamped at build time is compared against the app version and, on mismatch, shows an error dialog and quits.
  • The update install flow in DesktopUpdates.ts now requires each running backend to confirm a finalized stop within 10 seconds and, on Windows+WSL, calls ensureWindowsPathReleased to verify the install directory is free before invoking quitAndInstall; on failure it restarts backends and surfaces an error.
  • WSL readiness probing in DesktopBackendManager.ts now races multiple candidate URLs (loopback + all distro IPs) and optionally consults persisted runtime-state fallback URLs before timing out; stop() now returns a boolean indicating whether the process finalized before the timeout.
  • DesktopWslEnvironment.ts replaces the single-IP getDistroIp with getDistroIps (all IPv4s), and adds getNetworkingMode, readServerRuntimeState, and ensureWindowsPathReleased with fixed timeouts and fallback semantics.
  • Risk: backends that do not finalize within 10 seconds during an update will block the installer and restart, changing prior best-effort stop behavior.

Macroscope summarized 249a6d9.

…iness handshake
Two user-facing failures shared one splash-hang symptom; both are fixed here.
Update apply (half-applied install):
- instance.stop() now reports whether the child fully finalized; the
install path aborts (and restarts backends, surfacing the error through
the update state machine) instead of running NSIS while a backend that
holds install-dir handles may still be alive.
- Before handing off to the installer on Windows, verify from inside the
distro that no Linux process still holds cwd/exe/fd/mmap references
under /mnt/c/<install dir> (ensureWindowsPathReleased: TERM, then KILL,
then report busy). 9p handles held from WSL block file replacement on
the Windows side, which is how app.asar and app.asar.unpacked ended up
from different builds.
- Post-apply integrity check: the build stamps
apps/server/dist/desktop-build-manifest.json (asar-unpacked on Windows);
startup compares it against the asar version and refuses to launch a
half-applied install with an actionable repair dialog instead of an
opaque ERR_MODULE_NOT_FOUND crash.
WSL readiness handshake (indefinite 'Connecting to WSL'):
- Networking mode now comes from wslinfo --networking-mode instead of
inferring it from the FIRST hostname -I address (a WSL-side tailscaled
putting a CGNAT 100.64/10 address first made mirrored hosts look NAT'd
and pointed every probe at an unroutable IP).
- Readiness races loopback and every enumerated distro address; the first
candidate that answers wins and becomes the advertised base URL.
- After all static candidates time out, the server's persisted
server-runtime.json origin is consulted as a last-resort candidate.
- A run that spawns but never answers any probe is terminated and retried;
after 3 readiness timeouts the failure surfaces like an exhausted
preflight (dialog + Windows fallback on the primary, inline error on the
secondary) with the probed URLs named, instead of sitting on the splash
forever. Resolved mode and candidates are logged at startup.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Aug 10, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e310a9a5-be8d-496e-b0cb-2823e06b141c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 10, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect service conventions review: one convention violation found — the new readiness fallback path uses Effect.catchTag, which the conventions replace with Effect.catchTags({ ... }) even when recovering a single tag. Everything else (subpath namespace imports, service-member additions on DesktopWslEnvironment with matching layerTest stubs, environment-based dependency acquisition in DesktopUpdates.make, Schema.TaggedErrorClass attributes with an attribute-derived message) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f49d4ea5f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/desktop/src/app/DesktopInstallIntegrity.ts Outdated
Comment threadapps/desktop/src/updates/DesktopUpdates.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
@macroscopeapp

macroscopeappBot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces significant new runtime behavior beyond a simple fix: a startup integrity check that can block app launch, fundamental changes to WSL backend connection logic with multi-candidate probing, new update install abort conditions when backends don't fully stop, and new capabilities to kill Linux processes holding files. The ~2200 additions span multiple subsystems and warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Jgratton24and others added 2 commits August 10, 2026 08:34
- Recover known tags with Effect.catchTags (convention) in the readiness
fallback path.
- Anchor the install-dir holder scan: descendants match "$dir/" and the
directory itself matches as an exact readlink line, so /mnt/c/application
is no longer classified as a holder of /mnt/c/app.
- Split the release-check result: "unavailable" (wsl.exe would not spawn —
no running VM can hold /mnt handles) proceeds with a warning, while
"unknown" (timeout/path-translation failure against a distro that was
hosting this backend moments ago) now aborts the install.
- abortInstall restarts only instances that were running before teardown,
so a previously-stopped backend is not resurrected by a failed attempt.
- readServerRuntimeState reads the state-dir flavor the spawned backend
actually uses (dev/ for --dev-url runs, userdata/ for packaged) instead
of preferring a possibly-stale packaged file.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ess surfacing
- A packaged Windows build with NO unpacked build manifest is now treated
as a half-applied update (new app.asar over a pre-stamp unpacked tree)
instead of skipping the check — every Windows artifact that ships the
checker also ships the stamp. Platforms that pack the server dist inside
the asar still skip.
- A non-fatal surfacing on a primary that is already the Windows backend
(only reachable via readiness exhaustion) now shows one dialog and stops
instead of applying a no-op WSL fallback and looping dialog + restart
forever.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadapps/desktop/src/app/DesktopInstallIntegrity.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Jgratton24and others added 2 commits August 10, 2026 08:48
…WSL release always aborts
- The integrity check now distinguishes read failures: only a NotFound on
packaged Windows is treated as a half-applied update. An unreadable
manifest (access denied, I/O error) logs and skips instead of falsely
bricking a healthy install.
- Drop the "unavailable" proceed path from the WSL release check: a
release-script spawn failure happens after wslpath already succeeded
against the distro, so it cannot prove WSL is gone — every unverified
outcome now reads "unknown" and aborts the install. A machine whose WSL
layer is genuinely broken recovers on the next launch via the preflight
Windows fallback (no WSL config in the pool means no release check).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nstead of restarting forever
Ports the still-relevant piece of pingdotgg#3623: a backend that spawns fine but
keeps exiting before it ever answers a readiness probe previously
restarted silently forever (500ms-10s backoff, no cap, no surfacing).
After 5 consecutive never-ready exits the failure now surfaces through
onPreflightFailed — dialog + Windows fallback on the primary, inline
Connections error on the WSL secondary — exactly like exhausted preflight
and readiness failures.
The counter is scoped strictly to post-spawn exits: pre-spawn preflight
retries have their own counter, a crash after the backend was ready
resets the streak (an established backend crashing is a different failure
than one that never comes up), and the budget refreshes on ready, on
external stop, and on a fresh manual start so one cap firing does not
permanently remove the retry budget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 10, 2026
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 02343e8. Configure here.

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
…r-ready exit cap
A readiness-timeout termination closes the run scope, and the resulting
child exit previously ALSO incremented exitFailureAttempt — every
readiness timeout double-counted as a crash, so after a readiness-cap
recovery the leftover streak could fire a second, misleading "exited N
times" dialog just two real failures later. The readiness kill now marks
the run stopRequested before closing its scope, which both excludes it
from the exit cap and routes finalize to discardSession (the failure log
was already captured via persistFailureSnapshot). Regression test drives
five readiness cycles with a retrying onPreflightFailed and asserts the
exit cap never fires.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Jgratton24
Jgratton24force-pushed the fix/update-apply-wsl-handshake branch from 8e4c156 to 61c09f0CompareAugust 10, 2026 13:54
Comment threadapps/desktop/src/backend/DesktopBackendPool.ts Outdated
Jgratton24and others added 2 commits August 10, 2026 10:06
…SL-primary condition
resolvePrimary only selects the WSL backend when BOTH wslOnly and
wslBackendEnabled are set (wslRequested), but the non-fatal surfacing
branch checked wslOnly alone. With wslOnly persisted true while
wslBackendEnabled is false, a Windows primary exhausting readiness
retries took the WSL branch: a misleading "WSL backend is still
unavailable" dialog, an unrelated in-memory WSL settings mutation, and
one wasted retry cycle before the stop branch finally fired. The guard
now mirrors resolvePrimary's own condition, so a Windows primary
surfaces the generic backend-unavailable dialog and stops immediately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live testing on a real Windows+WSL host (WSL 2.7, mirrored) showed that
plain Linux fds and mmaps through 9p/DrvFs do NOT block Windows-side file
replacement, while a Windows-side handle does (the backend child runs as
the app executable and reads through app.asar). Comments now describe the
WSL release check as the defensive guarantee it is, with the verified stop
of Windows backend children as the confirmed-mechanism fix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ensureWindowsPathReleased spawned the holder scan as the default WSL
user. find_holders inspects /proc/<pid>/{cwd,exe,fd,maps}, which the
kernel exposes only to the process owner and root, so the unprivileged
scan silently got EACCES for any process it did not own. A root- or
other-user-owned process holding the install directory was therefore
invisible: the check returned RELEASED without ever seeing (let alone
killing) it, defeating the guard in exactly the case it exists for.
Spawn the probe as `wsl -u root` (WSL grants root with no password,
gated by the Windows host) so the scan sees every holder and its kills
land on holders it does not own. find_holders is already scoped to
processes that reference the install dir, so this cannot reach
unrelated processes.
Verified on Windows 11 + WSL2 (Ubuntu, mirrored): a root-owned holder
with cwd in the install dir that the old code left untouched is now
detected and SIGTERM-cleared before the installer handoff.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@SunkenInTimeChatGPT Codex Connector

Copy link
Copy Markdown
Contributor

Current head 249a6d93da1c094c9fe2f94af989a6377dcbe6cb is not merge-ready because the required Check job is red on formatting.

The failing job reports formatting issues in:

  • DesktopInstallIntegrity.ts
  • DesktopBackendConfiguration.test.ts / .ts
  • DesktopBackendManager.test.ts / .ts
  • DesktopUpdates.test.ts / .ts

Please run the repository formatter and push the result. I reproduced the functional focused suite on Windows at 126/127 passing; the one failure is the pre-existing Windows slash assertion in the external resource-monitor test, and both desktop and script-scope typechecks pass.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

2 participants

@Jgratton24@SunkenInTime
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(desktop): verified update teardown on Windows+WSL, install integrity check, and resilient WSL readiness handshake - #5998

Open
Jgratton24 wants to merge 9 commits into
pingdotgg:mainfrom
Jgratton24:fix/update-apply-wsl-handshake
Open

fix(desktop): verified update teardown on Windows+WSL, install integrity check, and resilient WSL readiness handshake#5998
Jgratton24 wants to merge 9 commits into
pingdotgg:mainfrom
Jgratton24:fix/update-apply-wsl-handshake

Conversation

@Jgratton24

@Jgratton24Jgratton24 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What Changed

Two Windows+WSL reliability fixes that share one user-visible symptom (an indefinite "Connecting to WSL…" splash), plus a guard that turns the corrupted-install crash into an actionable dialog. Desktop-only; no apps/server changes.

1. Update apply is verified before the installer runs (fixes the half-applied-update corruption).

  • DesktopBackendInstance.stop() now reports whether the child process actually finalized within the timeout instead of silently swallowing it (DesktopBackendManager.ts).
  • The install path (DesktopUpdates.ts) aborts — restarting backends and surfacing the error through the update state machine — if any backend can't be verified stopped, instead of handing off to NSIS while a process still holds files under the install dir.
  • For WSL backends, a stopped wsl.exe relay does not mean the Linux-side server (or helpers it spawned, e.g. cloudflared) exited with it. New ensureWindowsPathReleased (DesktopWslEnvironment.ts) terminates and verifies from inside the distro that no Linux process still holds cwd/exe/fd/mmap references under /mnt/c/<install dir> — 9p/DrvFs handles held from WSL block file replacement on the Windows side, which is how app.asar and app.asar.unpacked end up from different builds.
  • Post-apply integrity check: the build stamps apps/server/dist/desktop-build-manifest.json (under app.asar.unpacked on Windows); startup compares it against the asar's version and refuses to launch a mixed-build install with a clear repair dialog (DesktopInstallIntegrity.ts) instead of the opaque ERR_MODULE_NOT_FOUND main-process crash. Missing manifest (dev, macOS/Linux, pre-stamp installs) skips the check, so no false positives.

2. WSL endpoint detection and the readiness handshake are no longer single-point-of-failure.

  • Networking mode now comes from wslinfo --networking-mode (first-party, present on WSL 2.6.x and 2.7.x) instead of being inferred from the first address in hostname -I — whose ordering shifts whenever Tailscale, Docker bridges, or VPN adapters exist inside the distro. Heuristic fallback for older WSL matches any distro IP against Windows interfaces, never position one, and CGNAT 100.64/10 addresses are never advertised.
  • Readiness probes race loopback plus every enumerated distro address; the first candidate that answers wins and becomes the advertised base URL — so a wrong guess degrades startup by nothing worse than a probe race.
  • After all static candidates time out, the backend's persisted server-runtime.json origin is consulted as a last-resort candidate (gated on it naming this instance's port).
  • The readiness probe is no longer one-shot: a run that spawns but never answers is terminated and retried, and after 3 readiness timeouts the failure surfaces like an exhausted preflight (dialog + Windows fallback on the primary, inline Connections error on the secondary) with every probed URL named. Resolved mode and candidates are logged at startup.

25 new tests across DesktopBackendManager, DesktopUpdates, DesktopWslEnvironment, DesktopBackendConfiguration, and DesktopInstallIntegrity cover the regressions above (CGNAT-first ordering, docker0-first, wslinfo absent, one-shot-probe limbo, unverified stop, WSL busy at install time, mixed-build detection). Full desktop suite: 473 passing.

Why

Two field incidents on 0.0.33-nightly builds, plus these open issues:

The half-applied update we debugged: an in-app update left app.asar + the exe at the old build while app.asar.unpacked/node_modules was new → ERR_MODULE_NOT_FOUND on every launch, with the updater having reported nothing. Root cause: install teardown killed the wsl.exe relay and proceeded on a 5s timeout that was silently swallowed, while the Linux-side server exited asynchronously still holding 9p handles into the install dir. Same version installed fine with the app closed.

Sizing note: aware of the contribution guidelines — roughly half of this diff is tests, and the two fixes share the same touched files (DesktopBackendManager/DesktopWslEnvironment), which is why they're one PR. Happy to split into updater-atomicity and handshake PRs if you'd prefer.

This PR deliberately avoids every file touched by #2829 (orchestrator v2) — verified against its full file list, so it merges cleanly before or after.

UI Changes

No renderer UI changes. Two native dialogs are added/extended (update-install-aborted error via the existing update state machine; "installation is damaged" message box with an Open Download Page button). Not screenshotted — they require a packaged Windows build to trigger, which this dev environment can't produce; dialog copy is in DesktopInstallIntegrity.ts and DesktopUpdates.ts.

Checklist

  • This PR is small and focused (two related reliability fixes, no feature work; ~half the diff is tests)
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (native dialogs only; see UI Changes)
  • I included a video for animation/interaction changes (n/a)

🤖 Generated with Claude Code


Note

High Risk
Changes touch critical Windows update apply, WSL process killing (root), and startup abort paths; incorrect readiness or release checks could block launches or updates, though behavior is heavily tested.

Overview
This PR targets two reliability problems on Windows with WSL—indefinite “connecting” and half-applied updates—and adds a startup guard for corrupted installs.

WSL backend reachability no longer trusts the first hostname -I address. Networking mode comes from wslinfo --networking-mode (with heuristics when unknown), CGNAT 100.64/10 IPs are never advertised, and readiness races loopback plus every distro IP; the winning URL is rebound into currentConfig. After static probes time out, server-runtime.json supplies fallback candidates. Runs that never become ready are terminated and retried, with caps that surface failures via onPreflightFailed instead of hanging forever. getDistroIp is replaced by getDistroIps.

Updates require each backend stop() to return whether the child actually exited within a 10s budget; failed installs abort, restart backends, and leave the update downloadable. On Windows, ensureWindowsPathReleased verifies WSL has released the install directory (released / busy / unknown) before quitAndInstall.

Install integrity compares the packaged app version to desktop-build-manifest.json under app.asar.unpacked (stamped at build time); mismatches or a missing manifest on packaged Windows show a repair dialog and exit before bootstrap. The build script writes the manifest into staged apps/server/dist.

Reviewed by Cursor Bugbot for commit 249a6d9. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix update teardown on Windows+WSL with install integrity checks and resilient WSL readiness handshake

  • Startup now runs an install-integrity check via DesktopInstallIntegrity before bootstrapping; on Windows, a desktop-build-manifest.json stamped at build time is compared against the app version and, on mismatch, shows an error dialog and quits.
  • The update install flow in DesktopUpdates.ts now requires each running backend to confirm a finalized stop within 10 seconds and, on Windows+WSL, calls ensureWindowsPathReleased to verify the install directory is free before invoking quitAndInstall; on failure it restarts backends and surfaces an error.
  • WSL readiness probing in DesktopBackendManager.ts now races multiple candidate URLs (loopback + all distro IPs) and optionally consults persisted runtime-state fallback URLs before timing out; stop() now returns a boolean indicating whether the process finalized before the timeout.
  • DesktopWslEnvironment.ts replaces the single-IP getDistroIp with getDistroIps (all IPv4s), and adds getNetworkingMode, readServerRuntimeState, and ensureWindowsPathReleased with fixed timeouts and fallback semantics.
  • Risk: backends that do not finalize within 10 seconds during an update will block the installer and restart, changing prior best-effort stop behavior.

Macroscope summarized 249a6d9.

…iness handshake
Two user-facing failures shared one splash-hang symptom; both are fixed here.
Update apply (half-applied install):
- instance.stop() now reports whether the child fully finalized; the
install path aborts (and restarts backends, surfacing the error through
the update state machine) instead of running NSIS while a backend that
holds install-dir handles may still be alive.
- Before handing off to the installer on Windows, verify from inside the
distro that no Linux process still holds cwd/exe/fd/mmap references
under /mnt/c/<install dir> (ensureWindowsPathReleased: TERM, then KILL,
then report busy). 9p handles held from WSL block file replacement on
the Windows side, which is how app.asar and app.asar.unpacked ended up
from different builds.
- Post-apply integrity check: the build stamps
apps/server/dist/desktop-build-manifest.json (asar-unpacked on Windows);
startup compares it against the asar version and refuses to launch a
half-applied install with an actionable repair dialog instead of an
opaque ERR_MODULE_NOT_FOUND crash.
WSL readiness handshake (indefinite 'Connecting to WSL'):
- Networking mode now comes from wslinfo --networking-mode instead of
inferring it from the FIRST hostname -I address (a WSL-side tailscaled
putting a CGNAT 100.64/10 address first made mirrored hosts look NAT'd
and pointed every probe at an unroutable IP).
- Readiness races loopback and every enumerated distro address; the first
candidate that answers wins and becomes the advertised base URL.
- After all static candidates time out, the server's persisted
server-runtime.json origin is consulted as a last-resort candidate.
- A run that spawns but never answers any probe is terminated and retried;
after 3 readiness timeouts the failure surfaces like an exhausted
preflight (dialog + Windows fallback on the primary, inline error on the
secondary) with the probed URLs named, instead of sitting on the splash
forever. Resolved mode and candidates are logged at startup.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Aug 10, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e310a9a5-be8d-496e-b0cb-2823e06b141c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 10, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect service conventions review: one convention violation found — the new readiness fallback path uses Effect.catchTag, which the conventions replace with Effect.catchTags({ ... }) even when recovering a single tag. Everything else (subpath namespace imports, service-member additions on DesktopWslEnvironment with matching layerTest stubs, environment-based dependency acquisition in DesktopUpdates.make, Schema.TaggedErrorClass attributes with an attribute-derived message) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f49d4ea5f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/desktop/src/app/DesktopInstallIntegrity.ts Outdated
Comment threadapps/desktop/src/updates/DesktopUpdates.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
@macroscopeapp

macroscopeappBot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces significant new runtime behavior beyond a simple fix: a startup integrity check that can block app launch, fundamental changes to WSL backend connection logic with multi-candidate probing, new update install abort conditions when backends don't fully stop, and new capabilities to kill Linux processes holding files. The ~2200 additions span multiple subsystems and warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Jgratton24and others added 2 commits August 10, 2026 08:34
- Recover known tags with Effect.catchTags (convention) in the readiness
fallback path.
- Anchor the install-dir holder scan: descendants match "$dir/" and the
directory itself matches as an exact readlink line, so /mnt/c/application
is no longer classified as a holder of /mnt/c/app.
- Split the release-check result: "unavailable" (wsl.exe would not spawn —
no running VM can hold /mnt handles) proceeds with a warning, while
"unknown" (timeout/path-translation failure against a distro that was
hosting this backend moments ago) now aborts the install.
- abortInstall restarts only instances that were running before teardown,
so a previously-stopped backend is not resurrected by a failed attempt.
- readServerRuntimeState reads the state-dir flavor the spawned backend
actually uses (dev/ for --dev-url runs, userdata/ for packaged) instead
of preferring a possibly-stale packaged file.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ess surfacing
- A packaged Windows build with NO unpacked build manifest is now treated
as a half-applied update (new app.asar over a pre-stamp unpacked tree)
instead of skipping the check — every Windows artifact that ships the
checker also ships the stamp. Platforms that pack the server dist inside
the asar still skip.
- A non-fatal surfacing on a primary that is already the Windows backend
(only reachable via readiness exhaustion) now shows one dialog and stops
instead of applying a no-op WSL fallback and looping dialog + restart
forever.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadapps/desktop/src/app/DesktopInstallIntegrity.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Jgratton24and others added 2 commits August 10, 2026 08:48
…WSL release always aborts
- The integrity check now distinguishes read failures: only a NotFound on
packaged Windows is treated as a half-applied update. An unreadable
manifest (access denied, I/O error) logs and skips instead of falsely
bricking a healthy install.
- Drop the "unavailable" proceed path from the WSL release check: a
release-script spawn failure happens after wslpath already succeeded
against the distro, so it cannot prove WSL is gone — every unverified
outcome now reads "unknown" and aborts the install. A machine whose WSL
layer is genuinely broken recovers on the next launch via the preflight
Windows fallback (no WSL config in the pool means no release check).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nstead of restarting forever
Ports the still-relevant piece of pingdotgg#3623: a backend that spawns fine but
keeps exiting before it ever answers a readiness probe previously
restarted silently forever (500ms-10s backoff, no cap, no surfacing).
After 5 consecutive never-ready exits the failure now surfaces through
onPreflightFailed — dialog + Windows fallback on the primary, inline
Connections error on the WSL secondary — exactly like exhausted preflight
and readiness failures.
The counter is scoped strictly to post-spawn exits: pre-spawn preflight
retries have their own counter, a crash after the backend was ready
resets the streak (an established backend crashing is a different failure
than one that never comes up), and the budget refreshes on ready, on
external stop, and on a fresh manual start so one cap firing does not
permanently remove the retry budget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 10, 2026
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 02343e8. Configure here.

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
…r-ready exit cap
A readiness-timeout termination closes the run scope, and the resulting
child exit previously ALSO incremented exitFailureAttempt — every
readiness timeout double-counted as a crash, so after a readiness-cap
recovery the leftover streak could fire a second, misleading "exited N
times" dialog just two real failures later. The readiness kill now marks
the run stopRequested before closing its scope, which both excludes it
from the exit cap and routes finalize to discardSession (the failure log
was already captured via persistFailureSnapshot). Regression test drives
five readiness cycles with a retrying onPreflightFailed and asserts the
exit cap never fires.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Jgratton24
Jgratton24force-pushed the fix/update-apply-wsl-handshake branch from 8e4c156 to 61c09f0CompareAugust 10, 2026 13:54
Comment threadapps/desktop/src/backend/DesktopBackendPool.ts Outdated
Jgratton24and others added 2 commits August 10, 2026 10:06
…SL-primary condition
resolvePrimary only selects the WSL backend when BOTH wslOnly and
wslBackendEnabled are set (wslRequested), but the non-fatal surfacing
branch checked wslOnly alone. With wslOnly persisted true while
wslBackendEnabled is false, a Windows primary exhausting readiness
retries took the WSL branch: a misleading "WSL backend is still
unavailable" dialog, an unrelated in-memory WSL settings mutation, and
one wasted retry cycle before the stop branch finally fired. The guard
now mirrors resolvePrimary's own condition, so a Windows primary
surfaces the generic backend-unavailable dialog and stops immediately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live testing on a real Windows+WSL host (WSL 2.7, mirrored) showed that
plain Linux fds and mmaps through 9p/DrvFs do NOT block Windows-side file
replacement, while a Windows-side handle does (the backend child runs as
the app executable and reads through app.asar). Comments now describe the
WSL release check as the defensive guarantee it is, with the verified stop
of Windows backend children as the confirmed-mechanism fix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ensureWindowsPathReleased spawned the holder scan as the default WSL
user. find_holders inspects /proc/<pid>/{cwd,exe,fd,maps}, which the
kernel exposes only to the process owner and root, so the unprivileged
scan silently got EACCES for any process it did not own. A root- or
other-user-owned process holding the install directory was therefore
invisible: the check returned RELEASED without ever seeing (let alone
killing) it, defeating the guard in exactly the case it exists for.
Spawn the probe as `wsl -u root` (WSL grants root with no password,
gated by the Windows host) so the scan sees every holder and its kills
land on holders it does not own. find_holders is already scoped to
processes that reference the install dir, so this cannot reach
unrelated processes.
Verified on Windows 11 + WSL2 (Ubuntu, mirrored): a root-owned holder
with cwd in the install dir that the old code left untouched is now
detected and SIGTERM-cleared before the installer handoff.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@SunkenInTimeChatGPT Codex Connector

Copy link
Copy Markdown
Contributor

Current head 249a6d93da1c094c9fe2f94af989a6377dcbe6cb is not merge-ready because the required Check job is red on formatting.

The failing job reports formatting issues in:

  • DesktopInstallIntegrity.ts
  • DesktopBackendConfiguration.test.ts / .ts
  • DesktopBackendManager.test.ts / .ts
  • DesktopUpdates.test.ts / .ts

Please run the repository formatter and push the result. I reproduced the functional focused suite on Windows at 126/127 passing; the one failure is the pre-existing Windows slash assertion in the external resource-monitor test, and both desktop and script-scope typechecks pass.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

2 participants

@Jgratton24@SunkenInTime
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(desktop): verified update teardown on Windows+WSL, install integrity check, and resilient WSL readiness handshake - #5998

Open
Jgratton24 wants to merge 9 commits into
pingdotgg:mainfrom
Jgratton24:fix/update-apply-wsl-handshake
Open

fix(desktop): verified update teardown on Windows+WSL, install integrity check, and resilient WSL readiness handshake#5998
Jgratton24 wants to merge 9 commits into
pingdotgg:mainfrom
Jgratton24:fix/update-apply-wsl-handshake

Conversation

@Jgratton24

@Jgratton24Jgratton24 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What Changed

Two Windows+WSL reliability fixes that share one user-visible symptom (an indefinite "Connecting to WSL…" splash), plus a guard that turns the corrupted-install crash into an actionable dialog. Desktop-only; no apps/server changes.

1. Update apply is verified before the installer runs (fixes the half-applied-update corruption).

  • DesktopBackendInstance.stop() now reports whether the child process actually finalized within the timeout instead of silently swallowing it (DesktopBackendManager.ts).
  • The install path (DesktopUpdates.ts) aborts — restarting backends and surfacing the error through the update state machine — if any backend can't be verified stopped, instead of handing off to NSIS while a process still holds files under the install dir.
  • For WSL backends, a stopped wsl.exe relay does not mean the Linux-side server (or helpers it spawned, e.g. cloudflared) exited with it. New ensureWindowsPathReleased (DesktopWslEnvironment.ts) terminates and verifies from inside the distro that no Linux process still holds cwd/exe/fd/mmap references under /mnt/c/<install dir> — 9p/DrvFs handles held from WSL block file replacement on the Windows side, which is how app.asar and app.asar.unpacked end up from different builds.
  • Post-apply integrity check: the build stamps apps/server/dist/desktop-build-manifest.json (under app.asar.unpacked on Windows); startup compares it against the asar's version and refuses to launch a mixed-build install with a clear repair dialog (DesktopInstallIntegrity.ts) instead of the opaque ERR_MODULE_NOT_FOUND main-process crash. Missing manifest (dev, macOS/Linux, pre-stamp installs) skips the check, so no false positives.

2. WSL endpoint detection and the readiness handshake are no longer single-point-of-failure.

  • Networking mode now comes from wslinfo --networking-mode (first-party, present on WSL 2.6.x and 2.7.x) instead of being inferred from the first address in hostname -I — whose ordering shifts whenever Tailscale, Docker bridges, or VPN adapters exist inside the distro. Heuristic fallback for older WSL matches any distro IP against Windows interfaces, never position one, and CGNAT 100.64/10 addresses are never advertised.
  • Readiness probes race loopback plus every enumerated distro address; the first candidate that answers wins and becomes the advertised base URL — so a wrong guess degrades startup by nothing worse than a probe race.
  • After all static candidates time out, the backend's persisted server-runtime.json origin is consulted as a last-resort candidate (gated on it naming this instance's port).
  • The readiness probe is no longer one-shot: a run that spawns but never answers is terminated and retried, and after 3 readiness timeouts the failure surfaces like an exhausted preflight (dialog + Windows fallback on the primary, inline Connections error on the secondary) with every probed URL named. Resolved mode and candidates are logged at startup.

25 new tests across DesktopBackendManager, DesktopUpdates, DesktopWslEnvironment, DesktopBackendConfiguration, and DesktopInstallIntegrity cover the regressions above (CGNAT-first ordering, docker0-first, wslinfo absent, one-shot-probe limbo, unverified stop, WSL busy at install time, mixed-build detection). Full desktop suite: 473 passing.

Why

Two field incidents on 0.0.33-nightly builds, plus these open issues:

The half-applied update we debugged: an in-app update left app.asar + the exe at the old build while app.asar.unpacked/node_modules was new → ERR_MODULE_NOT_FOUND on every launch, with the updater having reported nothing. Root cause: install teardown killed the wsl.exe relay and proceeded on a 5s timeout that was silently swallowed, while the Linux-side server exited asynchronously still holding 9p handles into the install dir. Same version installed fine with the app closed.

Sizing note: aware of the contribution guidelines — roughly half of this diff is tests, and the two fixes share the same touched files (DesktopBackendManager/DesktopWslEnvironment), which is why they're one PR. Happy to split into updater-atomicity and handshake PRs if you'd prefer.

This PR deliberately avoids every file touched by #2829 (orchestrator v2) — verified against its full file list, so it merges cleanly before or after.

UI Changes

No renderer UI changes. Two native dialogs are added/extended (update-install-aborted error via the existing update state machine; "installation is damaged" message box with an Open Download Page button). Not screenshotted — they require a packaged Windows build to trigger, which this dev environment can't produce; dialog copy is in DesktopInstallIntegrity.ts and DesktopUpdates.ts.

Checklist

  • This PR is small and focused (two related reliability fixes, no feature work; ~half the diff is tests)
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (native dialogs only; see UI Changes)
  • I included a video for animation/interaction changes (n/a)

🤖 Generated with Claude Code


Note

High Risk
Changes touch critical Windows update apply, WSL process killing (root), and startup abort paths; incorrect readiness or release checks could block launches or updates, though behavior is heavily tested.

Overview
This PR targets two reliability problems on Windows with WSL—indefinite “connecting” and half-applied updates—and adds a startup guard for corrupted installs.

WSL backend reachability no longer trusts the first hostname -I address. Networking mode comes from wslinfo --networking-mode (with heuristics when unknown), CGNAT 100.64/10 IPs are never advertised, and readiness races loopback plus every distro IP; the winning URL is rebound into currentConfig. After static probes time out, server-runtime.json supplies fallback candidates. Runs that never become ready are terminated and retried, with caps that surface failures via onPreflightFailed instead of hanging forever. getDistroIp is replaced by getDistroIps.

Updates require each backend stop() to return whether the child actually exited within a 10s budget; failed installs abort, restart backends, and leave the update downloadable. On Windows, ensureWindowsPathReleased verifies WSL has released the install directory (released / busy / unknown) before quitAndInstall.

Install integrity compares the packaged app version to desktop-build-manifest.json under app.asar.unpacked (stamped at build time); mismatches or a missing manifest on packaged Windows show a repair dialog and exit before bootstrap. The build script writes the manifest into staged apps/server/dist.

Reviewed by Cursor Bugbot for commit 249a6d9. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix update teardown on Windows+WSL with install integrity checks and resilient WSL readiness handshake

  • Startup now runs an install-integrity check via DesktopInstallIntegrity before bootstrapping; on Windows, a desktop-build-manifest.json stamped at build time is compared against the app version and, on mismatch, shows an error dialog and quits.
  • The update install flow in DesktopUpdates.ts now requires each running backend to confirm a finalized stop within 10 seconds and, on Windows+WSL, calls ensureWindowsPathReleased to verify the install directory is free before invoking quitAndInstall; on failure it restarts backends and surfaces an error.
  • WSL readiness probing in DesktopBackendManager.ts now races multiple candidate URLs (loopback + all distro IPs) and optionally consults persisted runtime-state fallback URLs before timing out; stop() now returns a boolean indicating whether the process finalized before the timeout.
  • DesktopWslEnvironment.ts replaces the single-IP getDistroIp with getDistroIps (all IPv4s), and adds getNetworkingMode, readServerRuntimeState, and ensureWindowsPathReleased with fixed timeouts and fallback semantics.
  • Risk: backends that do not finalize within 10 seconds during an update will block the installer and restart, changing prior best-effort stop behavior.

Macroscope summarized 249a6d9.

…iness handshake
Two user-facing failures shared one splash-hang symptom; both are fixed here.
Update apply (half-applied install):
- instance.stop() now reports whether the child fully finalized; the
install path aborts (and restarts backends, surfacing the error through
the update state machine) instead of running NSIS while a backend that
holds install-dir handles may still be alive.
- Before handing off to the installer on Windows, verify from inside the
distro that no Linux process still holds cwd/exe/fd/mmap references
under /mnt/c/<install dir> (ensureWindowsPathReleased: TERM, then KILL,
then report busy). 9p handles held from WSL block file replacement on
the Windows side, which is how app.asar and app.asar.unpacked ended up
from different builds.
- Post-apply integrity check: the build stamps
apps/server/dist/desktop-build-manifest.json (asar-unpacked on Windows);
startup compares it against the asar version and refuses to launch a
half-applied install with an actionable repair dialog instead of an
opaque ERR_MODULE_NOT_FOUND crash.
WSL readiness handshake (indefinite 'Connecting to WSL'):
- Networking mode now comes from wslinfo --networking-mode instead of
inferring it from the FIRST hostname -I address (a WSL-side tailscaled
putting a CGNAT 100.64/10 address first made mirrored hosts look NAT'd
and pointed every probe at an unroutable IP).
- Readiness races loopback and every enumerated distro address; the first
candidate that answers wins and becomes the advertised base URL.
- After all static candidates time out, the server's persisted
server-runtime.json origin is consulted as a last-resort candidate.
- A run that spawns but never answers any probe is terminated and retried;
after 3 readiness timeouts the failure surfaces like an exhausted
preflight (dialog + Windows fallback on the primary, inline error on the
secondary) with the probed URLs named, instead of sitting on the splash
forever. Resolved mode and candidates are logged at startup.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Aug 10, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e310a9a5-be8d-496e-b0cb-2823e06b141c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 10, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect service conventions review: one convention violation found — the new readiness fallback path uses Effect.catchTag, which the conventions replace with Effect.catchTags({ ... }) even when recovering a single tag. Everything else (subpath namespace imports, service-member additions on DesktopWslEnvironment with matching layerTest stubs, environment-based dependency acquisition in DesktopUpdates.make, Schema.TaggedErrorClass attributes with an attribute-derived message) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f49d4ea5f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/desktop/src/app/DesktopInstallIntegrity.ts Outdated
Comment threadapps/desktop/src/updates/DesktopUpdates.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
@macroscopeapp

macroscopeappBot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces significant new runtime behavior beyond a simple fix: a startup integrity check that can block app launch, fundamental changes to WSL backend connection logic with multi-candidate probing, new update install abort conditions when backends don't fully stop, and new capabilities to kill Linux processes holding files. The ~2200 additions span multiple subsystems and warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Jgratton24and others added 2 commits August 10, 2026 08:34
- Recover known tags with Effect.catchTags (convention) in the readiness
fallback path.
- Anchor the install-dir holder scan: descendants match "$dir/" and the
directory itself matches as an exact readlink line, so /mnt/c/application
is no longer classified as a holder of /mnt/c/app.
- Split the release-check result: "unavailable" (wsl.exe would not spawn —
no running VM can hold /mnt handles) proceeds with a warning, while
"unknown" (timeout/path-translation failure against a distro that was
hosting this backend moments ago) now aborts the install.
- abortInstall restarts only instances that were running before teardown,
so a previously-stopped backend is not resurrected by a failed attempt.
- readServerRuntimeState reads the state-dir flavor the spawned backend
actually uses (dev/ for --dev-url runs, userdata/ for packaged) instead
of preferring a possibly-stale packaged file.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ess surfacing
- A packaged Windows build with NO unpacked build manifest is now treated
as a half-applied update (new app.asar over a pre-stamp unpacked tree)
instead of skipping the check — every Windows artifact that ships the
checker also ships the stamp. Platforms that pack the server dist inside
the asar still skip.
- A non-fatal surfacing on a primary that is already the Windows backend
(only reachable via readiness exhaustion) now shows one dialog and stops
instead of applying a no-op WSL fallback and looping dialog + restart
forever.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadapps/desktop/src/app/DesktopInstallIntegrity.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Jgratton24and others added 2 commits August 10, 2026 08:48
…WSL release always aborts
- The integrity check now distinguishes read failures: only a NotFound on
packaged Windows is treated as a half-applied update. An unreadable
manifest (access denied, I/O error) logs and skips instead of falsely
bricking a healthy install.
- Drop the "unavailable" proceed path from the WSL release check: a
release-script spawn failure happens after wslpath already succeeded
against the distro, so it cannot prove WSL is gone — every unverified
outcome now reads "unknown" and aborts the install. A machine whose WSL
layer is genuinely broken recovers on the next launch via the preflight
Windows fallback (no WSL config in the pool means no release check).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nstead of restarting forever
Ports the still-relevant piece of pingdotgg#3623: a backend that spawns fine but
keeps exiting before it ever answers a readiness probe previously
restarted silently forever (500ms-10s backoff, no cap, no surfacing).
After 5 consecutive never-ready exits the failure now surfaces through
onPreflightFailed — dialog + Windows fallback on the primary, inline
Connections error on the WSL secondary — exactly like exhausted preflight
and readiness failures.
The counter is scoped strictly to post-spawn exits: pre-spawn preflight
retries have their own counter, a crash after the backend was ready
resets the streak (an established backend crashing is a different failure
than one that never comes up), and the budget refreshes on ready, on
external stop, and on a fresh manual start so one cap firing does not
permanently remove the retry budget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 10, 2026
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 02343e8. Configure here.

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
…r-ready exit cap
A readiness-timeout termination closes the run scope, and the resulting
child exit previously ALSO incremented exitFailureAttempt — every
readiness timeout double-counted as a crash, so after a readiness-cap
recovery the leftover streak could fire a second, misleading "exited N
times" dialog just two real failures later. The readiness kill now marks
the run stopRequested before closing its scope, which both excludes it
from the exit cap and routes finalize to discardSession (the failure log
was already captured via persistFailureSnapshot). Regression test drives
five readiness cycles with a retrying onPreflightFailed and asserts the
exit cap never fires.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Jgratton24
Jgratton24force-pushed the fix/update-apply-wsl-handshake branch from 8e4c156 to 61c09f0CompareAugust 10, 2026 13:54
Comment threadapps/desktop/src/backend/DesktopBackendPool.ts Outdated
Jgratton24and others added 2 commits August 10, 2026 10:06
…SL-primary condition
resolvePrimary only selects the WSL backend when BOTH wslOnly and
wslBackendEnabled are set (wslRequested), but the non-fatal surfacing
branch checked wslOnly alone. With wslOnly persisted true while
wslBackendEnabled is false, a Windows primary exhausting readiness
retries took the WSL branch: a misleading "WSL backend is still
unavailable" dialog, an unrelated in-memory WSL settings mutation, and
one wasted retry cycle before the stop branch finally fired. The guard
now mirrors resolvePrimary's own condition, so a Windows primary
surfaces the generic backend-unavailable dialog and stops immediately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live testing on a real Windows+WSL host (WSL 2.7, mirrored) showed that
plain Linux fds and mmaps through 9p/DrvFs do NOT block Windows-side file
replacement, while a Windows-side handle does (the backend child runs as
the app executable and reads through app.asar). Comments now describe the
WSL release check as the defensive guarantee it is, with the verified stop
of Windows backend children as the confirmed-mechanism fix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ensureWindowsPathReleased spawned the holder scan as the default WSL
user. find_holders inspects /proc/<pid>/{cwd,exe,fd,maps}, which the
kernel exposes only to the process owner and root, so the unprivileged
scan silently got EACCES for any process it did not own. A root- or
other-user-owned process holding the install directory was therefore
invisible: the check returned RELEASED without ever seeing (let alone
killing) it, defeating the guard in exactly the case it exists for.
Spawn the probe as `wsl -u root` (WSL grants root with no password,
gated by the Windows host) so the scan sees every holder and its kills
land on holders it does not own. find_holders is already scoped to
processes that reference the install dir, so this cannot reach
unrelated processes.
Verified on Windows 11 + WSL2 (Ubuntu, mirrored): a root-owned holder
with cwd in the install dir that the old code left untouched is now
detected and SIGTERM-cleared before the installer handoff.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@SunkenInTimeChatGPT Codex Connector

Copy link
Copy Markdown
Contributor

Current head 249a6d93da1c094c9fe2f94af989a6377dcbe6cb is not merge-ready because the required Check job is red on formatting.

The failing job reports formatting issues in:

  • DesktopInstallIntegrity.ts
  • DesktopBackendConfiguration.test.ts / .ts
  • DesktopBackendManager.test.ts / .ts
  • DesktopUpdates.test.ts / .ts

Please run the repository formatter and push the result. I reproduced the functional focused suite on Windows at 126/127 passing; the one failure is the pre-existing Windows slash assertion in the external resource-monitor test, and both desktop and script-scope typechecks pass.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

2 participants

@Jgratton24@SunkenInTime
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(desktop): verified update teardown on Windows+WSL, install integrity check, and resilient WSL readiness handshake - #5998

Open
Jgratton24 wants to merge 9 commits into
pingdotgg:mainfrom
Jgratton24:fix/update-apply-wsl-handshake
Open

fix(desktop): verified update teardown on Windows+WSL, install integrity check, and resilient WSL readiness handshake#5998
Jgratton24 wants to merge 9 commits into
pingdotgg:mainfrom
Jgratton24:fix/update-apply-wsl-handshake

Conversation

@Jgratton24

@Jgratton24Jgratton24 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What Changed

Two Windows+WSL reliability fixes that share one user-visible symptom (an indefinite "Connecting to WSL…" splash), plus a guard that turns the corrupted-install crash into an actionable dialog. Desktop-only; no apps/server changes.

1. Update apply is verified before the installer runs (fixes the half-applied-update corruption).

  • DesktopBackendInstance.stop() now reports whether the child process actually finalized within the timeout instead of silently swallowing it (DesktopBackendManager.ts).
  • The install path (DesktopUpdates.ts) aborts — restarting backends and surfacing the error through the update state machine — if any backend can't be verified stopped, instead of handing off to NSIS while a process still holds files under the install dir.
  • For WSL backends, a stopped wsl.exe relay does not mean the Linux-side server (or helpers it spawned, e.g. cloudflared) exited with it. New ensureWindowsPathReleased (DesktopWslEnvironment.ts) terminates and verifies from inside the distro that no Linux process still holds cwd/exe/fd/mmap references under /mnt/c/<install dir> — 9p/DrvFs handles held from WSL block file replacement on the Windows side, which is how app.asar and app.asar.unpacked end up from different builds.
  • Post-apply integrity check: the build stamps apps/server/dist/desktop-build-manifest.json (under app.asar.unpacked on Windows); startup compares it against the asar's version and refuses to launch a mixed-build install with a clear repair dialog (DesktopInstallIntegrity.ts) instead of the opaque ERR_MODULE_NOT_FOUND main-process crash. Missing manifest (dev, macOS/Linux, pre-stamp installs) skips the check, so no false positives.

2. WSL endpoint detection and the readiness handshake are no longer single-point-of-failure.

  • Networking mode now comes from wslinfo --networking-mode (first-party, present on WSL 2.6.x and 2.7.x) instead of being inferred from the first address in hostname -I — whose ordering shifts whenever Tailscale, Docker bridges, or VPN adapters exist inside the distro. Heuristic fallback for older WSL matches any distro IP against Windows interfaces, never position one, and CGNAT 100.64/10 addresses are never advertised.
  • Readiness probes race loopback plus every enumerated distro address; the first candidate that answers wins and becomes the advertised base URL — so a wrong guess degrades startup by nothing worse than a probe race.
  • After all static candidates time out, the backend's persisted server-runtime.json origin is consulted as a last-resort candidate (gated on it naming this instance's port).
  • The readiness probe is no longer one-shot: a run that spawns but never answers is terminated and retried, and after 3 readiness timeouts the failure surfaces like an exhausted preflight (dialog + Windows fallback on the primary, inline Connections error on the secondary) with every probed URL named. Resolved mode and candidates are logged at startup.

25 new tests across DesktopBackendManager, DesktopUpdates, DesktopWslEnvironment, DesktopBackendConfiguration, and DesktopInstallIntegrity cover the regressions above (CGNAT-first ordering, docker0-first, wslinfo absent, one-shot-probe limbo, unverified stop, WSL busy at install time, mixed-build detection). Full desktop suite: 473 passing.

Why

Two field incidents on 0.0.33-nightly builds, plus these open issues:

The half-applied update we debugged: an in-app update left app.asar + the exe at the old build while app.asar.unpacked/node_modules was new → ERR_MODULE_NOT_FOUND on every launch, with the updater having reported nothing. Root cause: install teardown killed the wsl.exe relay and proceeded on a 5s timeout that was silently swallowed, while the Linux-side server exited asynchronously still holding 9p handles into the install dir. Same version installed fine with the app closed.

Sizing note: aware of the contribution guidelines — roughly half of this diff is tests, and the two fixes share the same touched files (DesktopBackendManager/DesktopWslEnvironment), which is why they're one PR. Happy to split into updater-atomicity and handshake PRs if you'd prefer.

This PR deliberately avoids every file touched by #2829 (orchestrator v2) — verified against its full file list, so it merges cleanly before or after.

UI Changes

No renderer UI changes. Two native dialogs are added/extended (update-install-aborted error via the existing update state machine; "installation is damaged" message box with an Open Download Page button). Not screenshotted — they require a packaged Windows build to trigger, which this dev environment can't produce; dialog copy is in DesktopInstallIntegrity.ts and DesktopUpdates.ts.

Checklist

  • This PR is small and focused (two related reliability fixes, no feature work; ~half the diff is tests)
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (native dialogs only; see UI Changes)
  • I included a video for animation/interaction changes (n/a)

🤖 Generated with Claude Code


Note

High Risk
Changes touch critical Windows update apply, WSL process killing (root), and startup abort paths; incorrect readiness or release checks could block launches or updates, though behavior is heavily tested.

Overview
This PR targets two reliability problems on Windows with WSL—indefinite “connecting” and half-applied updates—and adds a startup guard for corrupted installs.

WSL backend reachability no longer trusts the first hostname -I address. Networking mode comes from wslinfo --networking-mode (with heuristics when unknown), CGNAT 100.64/10 IPs are never advertised, and readiness races loopback plus every distro IP; the winning URL is rebound into currentConfig. After static probes time out, server-runtime.json supplies fallback candidates. Runs that never become ready are terminated and retried, with caps that surface failures via onPreflightFailed instead of hanging forever. getDistroIp is replaced by getDistroIps.

Updates require each backend stop() to return whether the child actually exited within a 10s budget; failed installs abort, restart backends, and leave the update downloadable. On Windows, ensureWindowsPathReleased verifies WSL has released the install directory (released / busy / unknown) before quitAndInstall.

Install integrity compares the packaged app version to desktop-build-manifest.json under app.asar.unpacked (stamped at build time); mismatches or a missing manifest on packaged Windows show a repair dialog and exit before bootstrap. The build script writes the manifest into staged apps/server/dist.

Reviewed by Cursor Bugbot for commit 249a6d9. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix update teardown on Windows+WSL with install integrity checks and resilient WSL readiness handshake

  • Startup now runs an install-integrity check via DesktopInstallIntegrity before bootstrapping; on Windows, a desktop-build-manifest.json stamped at build time is compared against the app version and, on mismatch, shows an error dialog and quits.
  • The update install flow in DesktopUpdates.ts now requires each running backend to confirm a finalized stop within 10 seconds and, on Windows+WSL, calls ensureWindowsPathReleased to verify the install directory is free before invoking quitAndInstall; on failure it restarts backends and surfaces an error.
  • WSL readiness probing in DesktopBackendManager.ts now races multiple candidate URLs (loopback + all distro IPs) and optionally consults persisted runtime-state fallback URLs before timing out; stop() now returns a boolean indicating whether the process finalized before the timeout.
  • DesktopWslEnvironment.ts replaces the single-IP getDistroIp with getDistroIps (all IPv4s), and adds getNetworkingMode, readServerRuntimeState, and ensureWindowsPathReleased with fixed timeouts and fallback semantics.
  • Risk: backends that do not finalize within 10 seconds during an update will block the installer and restart, changing prior best-effort stop behavior.

Macroscope summarized 249a6d9.

…iness handshake
Two user-facing failures shared one splash-hang symptom; both are fixed here.
Update apply (half-applied install):
- instance.stop() now reports whether the child fully finalized; the
install path aborts (and restarts backends, surfacing the error through
the update state machine) instead of running NSIS while a backend that
holds install-dir handles may still be alive.
- Before handing off to the installer on Windows, verify from inside the
distro that no Linux process still holds cwd/exe/fd/mmap references
under /mnt/c/<install dir> (ensureWindowsPathReleased: TERM, then KILL,
then report busy). 9p handles held from WSL block file replacement on
the Windows side, which is how app.asar and app.asar.unpacked ended up
from different builds.
- Post-apply integrity check: the build stamps
apps/server/dist/desktop-build-manifest.json (asar-unpacked on Windows);
startup compares it against the asar version and refuses to launch a
half-applied install with an actionable repair dialog instead of an
opaque ERR_MODULE_NOT_FOUND crash.
WSL readiness handshake (indefinite 'Connecting to WSL'):
- Networking mode now comes from wslinfo --networking-mode instead of
inferring it from the FIRST hostname -I address (a WSL-side tailscaled
putting a CGNAT 100.64/10 address first made mirrored hosts look NAT'd
and pointed every probe at an unroutable IP).
- Readiness races loopback and every enumerated distro address; the first
candidate that answers wins and becomes the advertised base URL.
- After all static candidates time out, the server's persisted
server-runtime.json origin is consulted as a last-resort candidate.
- A run that spawns but never answers any probe is terminated and retried;
after 3 readiness timeouts the failure surfaces like an exhausted
preflight (dialog + Windows fallback on the primary, inline error on the
secondary) with the probed URLs named, instead of sitting on the splash
forever. Resolved mode and candidates are logged at startup.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Aug 10, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e310a9a5-be8d-496e-b0cb-2823e06b141c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 10, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect service conventions review: one convention violation found — the new readiness fallback path uses Effect.catchTag, which the conventions replace with Effect.catchTags({ ... }) even when recovering a single tag. Everything else (subpath namespace imports, service-member additions on DesktopWslEnvironment with matching layerTest stubs, environment-based dependency acquisition in DesktopUpdates.make, Schema.TaggedErrorClass attributes with an attribute-derived message) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f49d4ea5f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/desktop/src/app/DesktopInstallIntegrity.ts Outdated
Comment threadapps/desktop/src/updates/DesktopUpdates.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
@macroscopeapp

macroscopeappBot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces significant new runtime behavior beyond a simple fix: a startup integrity check that can block app launch, fundamental changes to WSL backend connection logic with multi-candidate probing, new update install abort conditions when backends don't fully stop, and new capabilities to kill Linux processes holding files. The ~2200 additions span multiple subsystems and warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Jgratton24and others added 2 commits August 10, 2026 08:34
- Recover known tags with Effect.catchTags (convention) in the readiness
fallback path.
- Anchor the install-dir holder scan: descendants match "$dir/" and the
directory itself matches as an exact readlink line, so /mnt/c/application
is no longer classified as a holder of /mnt/c/app.
- Split the release-check result: "unavailable" (wsl.exe would not spawn —
no running VM can hold /mnt handles) proceeds with a warning, while
"unknown" (timeout/path-translation failure against a distro that was
hosting this backend moments ago) now aborts the install.
- abortInstall restarts only instances that were running before teardown,
so a previously-stopped backend is not resurrected by a failed attempt.
- readServerRuntimeState reads the state-dir flavor the spawned backend
actually uses (dev/ for --dev-url runs, userdata/ for packaged) instead
of preferring a possibly-stale packaged file.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ess surfacing
- A packaged Windows build with NO unpacked build manifest is now treated
as a half-applied update (new app.asar over a pre-stamp unpacked tree)
instead of skipping the check — every Windows artifact that ships the
checker also ships the stamp. Platforms that pack the server dist inside
the asar still skip.
- A non-fatal surfacing on a primary that is already the Windows backend
(only reachable via readiness exhaustion) now shows one dialog and stops
instead of applying a no-op WSL fallback and looping dialog + restart
forever.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadapps/desktop/src/app/DesktopInstallIntegrity.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Jgratton24and others added 2 commits August 10, 2026 08:48
…WSL release always aborts
- The integrity check now distinguishes read failures: only a NotFound on
packaged Windows is treated as a half-applied update. An unreadable
manifest (access denied, I/O error) logs and skips instead of falsely
bricking a healthy install.
- Drop the "unavailable" proceed path from the WSL release check: a
release-script spawn failure happens after wslpath already succeeded
against the distro, so it cannot prove WSL is gone — every unverified
outcome now reads "unknown" and aborts the install. A machine whose WSL
layer is genuinely broken recovers on the next launch via the preflight
Windows fallback (no WSL config in the pool means no release check).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nstead of restarting forever
Ports the still-relevant piece of pingdotgg#3623: a backend that spawns fine but
keeps exiting before it ever answers a readiness probe previously
restarted silently forever (500ms-10s backoff, no cap, no surfacing).
After 5 consecutive never-ready exits the failure now surfaces through
onPreflightFailed — dialog + Windows fallback on the primary, inline
Connections error on the WSL secondary — exactly like exhausted preflight
and readiness failures.
The counter is scoped strictly to post-spawn exits: pre-spawn preflight
retries have their own counter, a crash after the backend was ready
resets the streak (an established backend crashing is a different failure
than one that never comes up), and the budget refreshes on ready, on
external stop, and on a fresh manual start so one cap firing does not
permanently remove the retry budget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 10, 2026
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 02343e8. Configure here.

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
…r-ready exit cap
A readiness-timeout termination closes the run scope, and the resulting
child exit previously ALSO incremented exitFailureAttempt — every
readiness timeout double-counted as a crash, so after a readiness-cap
recovery the leftover streak could fire a second, misleading "exited N
times" dialog just two real failures later. The readiness kill now marks
the run stopRequested before closing its scope, which both excludes it
from the exit cap and routes finalize to discardSession (the failure log
was already captured via persistFailureSnapshot). Regression test drives
five readiness cycles with a retrying onPreflightFailed and asserts the
exit cap never fires.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Jgratton24
Jgratton24force-pushed the fix/update-apply-wsl-handshake branch from 8e4c156 to 61c09f0CompareAugust 10, 2026 13:54
Comment threadapps/desktop/src/backend/DesktopBackendPool.ts Outdated
Jgratton24and others added 2 commits August 10, 2026 10:06
…SL-primary condition
resolvePrimary only selects the WSL backend when BOTH wslOnly and
wslBackendEnabled are set (wslRequested), but the non-fatal surfacing
branch checked wslOnly alone. With wslOnly persisted true while
wslBackendEnabled is false, a Windows primary exhausting readiness
retries took the WSL branch: a misleading "WSL backend is still
unavailable" dialog, an unrelated in-memory WSL settings mutation, and
one wasted retry cycle before the stop branch finally fired. The guard
now mirrors resolvePrimary's own condition, so a Windows primary
surfaces the generic backend-unavailable dialog and stops immediately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live testing on a real Windows+WSL host (WSL 2.7, mirrored) showed that
plain Linux fds and mmaps through 9p/DrvFs do NOT block Windows-side file
replacement, while a Windows-side handle does (the backend child runs as
the app executable and reads through app.asar). Comments now describe the
WSL release check as the defensive guarantee it is, with the verified stop
of Windows backend children as the confirmed-mechanism fix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ensureWindowsPathReleased spawned the holder scan as the default WSL
user. find_holders inspects /proc/<pid>/{cwd,exe,fd,maps}, which the
kernel exposes only to the process owner and root, so the unprivileged
scan silently got EACCES for any process it did not own. A root- or
other-user-owned process holding the install directory was therefore
invisible: the check returned RELEASED without ever seeing (let alone
killing) it, defeating the guard in exactly the case it exists for.
Spawn the probe as `wsl -u root` (WSL grants root with no password,
gated by the Windows host) so the scan sees every holder and its kills
land on holders it does not own. find_holders is already scoped to
processes that reference the install dir, so this cannot reach
unrelated processes.
Verified on Windows 11 + WSL2 (Ubuntu, mirrored): a root-owned holder
with cwd in the install dir that the old code left untouched is now
detected and SIGTERM-cleared before the installer handoff.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@SunkenInTimeChatGPT Codex Connector

Copy link
Copy Markdown
Contributor

Current head 249a6d93da1c094c9fe2f94af989a6377dcbe6cb is not merge-ready because the required Check job is red on formatting.

The failing job reports formatting issues in:

  • DesktopInstallIntegrity.ts
  • DesktopBackendConfiguration.test.ts / .ts
  • DesktopBackendManager.test.ts / .ts
  • DesktopUpdates.test.ts / .ts

Please run the repository formatter and push the result. I reproduced the functional focused suite on Windows at 126/127 passing; the one failure is the pre-existing Windows slash assertion in the external resource-monitor test, and both desktop and script-scope typechecks pass.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

2 participants

@Jgratton24@SunkenInTime
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(desktop): verified update teardown on Windows+WSL, install integrity check, and resilient WSL readiness handshake - #5998

Open
Jgratton24 wants to merge 9 commits into
pingdotgg:mainfrom
Jgratton24:fix/update-apply-wsl-handshake
Open

fix(desktop): verified update teardown on Windows+WSL, install integrity check, and resilient WSL readiness handshake#5998
Jgratton24 wants to merge 9 commits into
pingdotgg:mainfrom
Jgratton24:fix/update-apply-wsl-handshake

Conversation

@Jgratton24

@Jgratton24Jgratton24 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What Changed

Two Windows+WSL reliability fixes that share one user-visible symptom (an indefinite "Connecting to WSL…" splash), plus a guard that turns the corrupted-install crash into an actionable dialog. Desktop-only; no apps/server changes.

1. Update apply is verified before the installer runs (fixes the half-applied-update corruption).

  • DesktopBackendInstance.stop() now reports whether the child process actually finalized within the timeout instead of silently swallowing it (DesktopBackendManager.ts).
  • The install path (DesktopUpdates.ts) aborts — restarting backends and surfacing the error through the update state machine — if any backend can't be verified stopped, instead of handing off to NSIS while a process still holds files under the install dir.
  • For WSL backends, a stopped wsl.exe relay does not mean the Linux-side server (or helpers it spawned, e.g. cloudflared) exited with it. New ensureWindowsPathReleased (DesktopWslEnvironment.ts) terminates and verifies from inside the distro that no Linux process still holds cwd/exe/fd/mmap references under /mnt/c/<install dir> — 9p/DrvFs handles held from WSL block file replacement on the Windows side, which is how app.asar and app.asar.unpacked end up from different builds.
  • Post-apply integrity check: the build stamps apps/server/dist/desktop-build-manifest.json (under app.asar.unpacked on Windows); startup compares it against the asar's version and refuses to launch a mixed-build install with a clear repair dialog (DesktopInstallIntegrity.ts) instead of the opaque ERR_MODULE_NOT_FOUND main-process crash. Missing manifest (dev, macOS/Linux, pre-stamp installs) skips the check, so no false positives.

2. WSL endpoint detection and the readiness handshake are no longer single-point-of-failure.

  • Networking mode now comes from wslinfo --networking-mode (first-party, present on WSL 2.6.x and 2.7.x) instead of being inferred from the first address in hostname -I — whose ordering shifts whenever Tailscale, Docker bridges, or VPN adapters exist inside the distro. Heuristic fallback for older WSL matches any distro IP against Windows interfaces, never position one, and CGNAT 100.64/10 addresses are never advertised.
  • Readiness probes race loopback plus every enumerated distro address; the first candidate that answers wins and becomes the advertised base URL — so a wrong guess degrades startup by nothing worse than a probe race.
  • After all static candidates time out, the backend's persisted server-runtime.json origin is consulted as a last-resort candidate (gated on it naming this instance's port).
  • The readiness probe is no longer one-shot: a run that spawns but never answers is terminated and retried, and after 3 readiness timeouts the failure surfaces like an exhausted preflight (dialog + Windows fallback on the primary, inline Connections error on the secondary) with every probed URL named. Resolved mode and candidates are logged at startup.

25 new tests across DesktopBackendManager, DesktopUpdates, DesktopWslEnvironment, DesktopBackendConfiguration, and DesktopInstallIntegrity cover the regressions above (CGNAT-first ordering, docker0-first, wslinfo absent, one-shot-probe limbo, unverified stop, WSL busy at install time, mixed-build detection). Full desktop suite: 473 passing.

Why

Two field incidents on 0.0.33-nightly builds, plus these open issues:

The half-applied update we debugged: an in-app update left app.asar + the exe at the old build while app.asar.unpacked/node_modules was new → ERR_MODULE_NOT_FOUND on every launch, with the updater having reported nothing. Root cause: install teardown killed the wsl.exe relay and proceeded on a 5s timeout that was silently swallowed, while the Linux-side server exited asynchronously still holding 9p handles into the install dir. Same version installed fine with the app closed.

Sizing note: aware of the contribution guidelines — roughly half of this diff is tests, and the two fixes share the same touched files (DesktopBackendManager/DesktopWslEnvironment), which is why they're one PR. Happy to split into updater-atomicity and handshake PRs if you'd prefer.

This PR deliberately avoids every file touched by #2829 (orchestrator v2) — verified against its full file list, so it merges cleanly before or after.

UI Changes

No renderer UI changes. Two native dialogs are added/extended (update-install-aborted error via the existing update state machine; "installation is damaged" message box with an Open Download Page button). Not screenshotted — they require a packaged Windows build to trigger, which this dev environment can't produce; dialog copy is in DesktopInstallIntegrity.ts and DesktopUpdates.ts.

Checklist

  • This PR is small and focused (two related reliability fixes, no feature work; ~half the diff is tests)
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (native dialogs only; see UI Changes)
  • I included a video for animation/interaction changes (n/a)

🤖 Generated with Claude Code


Note

High Risk
Changes touch critical Windows update apply, WSL process killing (root), and startup abort paths; incorrect readiness or release checks could block launches or updates, though behavior is heavily tested.

Overview
This PR targets two reliability problems on Windows with WSL—indefinite “connecting” and half-applied updates—and adds a startup guard for corrupted installs.

WSL backend reachability no longer trusts the first hostname -I address. Networking mode comes from wslinfo --networking-mode (with heuristics when unknown), CGNAT 100.64/10 IPs are never advertised, and readiness races loopback plus every distro IP; the winning URL is rebound into currentConfig. After static probes time out, server-runtime.json supplies fallback candidates. Runs that never become ready are terminated and retried, with caps that surface failures via onPreflightFailed instead of hanging forever. getDistroIp is replaced by getDistroIps.

Updates require each backend stop() to return whether the child actually exited within a 10s budget; failed installs abort, restart backends, and leave the update downloadable. On Windows, ensureWindowsPathReleased verifies WSL has released the install directory (released / busy / unknown) before quitAndInstall.

Install integrity compares the packaged app version to desktop-build-manifest.json under app.asar.unpacked (stamped at build time); mismatches or a missing manifest on packaged Windows show a repair dialog and exit before bootstrap. The build script writes the manifest into staged apps/server/dist.

Reviewed by Cursor Bugbot for commit 249a6d9. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix update teardown on Windows+WSL with install integrity checks and resilient WSL readiness handshake

  • Startup now runs an install-integrity check via DesktopInstallIntegrity before bootstrapping; on Windows, a desktop-build-manifest.json stamped at build time is compared against the app version and, on mismatch, shows an error dialog and quits.
  • The update install flow in DesktopUpdates.ts now requires each running backend to confirm a finalized stop within 10 seconds and, on Windows+WSL, calls ensureWindowsPathReleased to verify the install directory is free before invoking quitAndInstall; on failure it restarts backends and surfaces an error.
  • WSL readiness probing in DesktopBackendManager.ts now races multiple candidate URLs (loopback + all distro IPs) and optionally consults persisted runtime-state fallback URLs before timing out; stop() now returns a boolean indicating whether the process finalized before the timeout.
  • DesktopWslEnvironment.ts replaces the single-IP getDistroIp with getDistroIps (all IPv4s), and adds getNetworkingMode, readServerRuntimeState, and ensureWindowsPathReleased with fixed timeouts and fallback semantics.
  • Risk: backends that do not finalize within 10 seconds during an update will block the installer and restart, changing prior best-effort stop behavior.

Macroscope summarized 249a6d9.

…iness handshake
Two user-facing failures shared one splash-hang symptom; both are fixed here.
Update apply (half-applied install):
- instance.stop() now reports whether the child fully finalized; the
install path aborts (and restarts backends, surfacing the error through
the update state machine) instead of running NSIS while a backend that
holds install-dir handles may still be alive.
- Before handing off to the installer on Windows, verify from inside the
distro that no Linux process still holds cwd/exe/fd/mmap references
under /mnt/c/<install dir> (ensureWindowsPathReleased: TERM, then KILL,
then report busy). 9p handles held from WSL block file replacement on
the Windows side, which is how app.asar and app.asar.unpacked ended up
from different builds.
- Post-apply integrity check: the build stamps
apps/server/dist/desktop-build-manifest.json (asar-unpacked on Windows);
startup compares it against the asar version and refuses to launch a
half-applied install with an actionable repair dialog instead of an
opaque ERR_MODULE_NOT_FOUND crash.
WSL readiness handshake (indefinite 'Connecting to WSL'):
- Networking mode now comes from wslinfo --networking-mode instead of
inferring it from the FIRST hostname -I address (a WSL-side tailscaled
putting a CGNAT 100.64/10 address first made mirrored hosts look NAT'd
and pointed every probe at an unroutable IP).
- Readiness races loopback and every enumerated distro address; the first
candidate that answers wins and becomes the advertised base URL.
- After all static candidates time out, the server's persisted
server-runtime.json origin is consulted as a last-resort candidate.
- A run that spawns but never answers any probe is terminated and retried;
after 3 readiness timeouts the failure surfaces like an exhausted
preflight (dialog + Windows fallback on the primary, inline error on the
secondary) with the probed URLs named, instead of sitting on the splash
forever. Resolved mode and candidates are logged at startup.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Aug 10, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e310a9a5-be8d-496e-b0cb-2823e06b141c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 10, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect service conventions review: one convention violation found — the new readiness fallback path uses Effect.catchTag, which the conventions replace with Effect.catchTags({ ... }) even when recovering a single tag. Everything else (subpath namespace imports, service-member additions on DesktopWslEnvironment with matching layerTest stubs, environment-based dependency acquisition in DesktopUpdates.make, Schema.TaggedErrorClass attributes with an attribute-derived message) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:f49d4ea5f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadapps/desktop/src/app/DesktopInstallIntegrity.ts Outdated
Comment threadapps/desktop/src/updates/DesktopUpdates.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
Comment threadapps/desktop/src/updates/DesktopUpdates.ts
@macroscopeapp

macroscopeappBot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces significant new runtime behavior beyond a simple fix: a startup integrity check that can block app launch, fundamental changes to WSL backend connection logic with multi-candidate probing, new update install abort conditions when backends don't fully stop, and new capabilities to kill Linux processes holding files. The ~2200 additions span multiple subsystems and warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Jgratton24and others added 2 commits August 10, 2026 08:34
- Recover known tags with Effect.catchTags (convention) in the readiness
fallback path.
- Anchor the install-dir holder scan: descendants match "$dir/" and the
directory itself matches as an exact readlink line, so /mnt/c/application
is no longer classified as a holder of /mnt/c/app.
- Split the release-check result: "unavailable" (wsl.exe would not spawn —
no running VM can hold /mnt handles) proceeds with a warning, while
"unknown" (timeout/path-translation failure against a distro that was
hosting this backend moments ago) now aborts the install.
- abortInstall restarts only instances that were running before teardown,
so a previously-stopped backend is not resurrected by a failed attempt.
- readServerRuntimeState reads the state-dir flavor the spawned backend
actually uses (dev/ for --dev-url runs, userdata/ for packaged) instead
of preferring a possibly-stale packaged file.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ess surfacing
- A packaged Windows build with NO unpacked build manifest is now treated
as a half-applied update (new app.asar over a pre-stamp unpacked tree)
instead of skipping the check — every Windows artifact that ships the
checker also ships the stamp. Platforms that pack the server dist inside
the asar still skip.
- A non-fatal surfacing on a primary that is already the Windows backend
(only reachable via readiness exhaustion) now shows one dialog and stops
instead of applying a no-op WSL fallback and looping dialog + restart
forever.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadapps/desktop/src/app/DesktopInstallIntegrity.ts Outdated
Comment threadapps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Jgratton24and others added 2 commits August 10, 2026 08:48
…WSL release always aborts
- The integrity check now distinguishes read failures: only a NotFound on
packaged Windows is treated as a half-applied update. An unreadable
manifest (access denied, I/O error) logs and skips instead of falsely
bricking a healthy install.
- Drop the "unavailable" proceed path from the WSL release check: a
release-script spawn failure happens after wslpath already succeeded
against the distro, so it cannot prove WSL is gone — every unverified
outcome now reads "unknown" and aborts the install. A machine whose WSL
layer is genuinely broken recovers on the next launch via the preflight
Windows fallback (no WSL config in the pool means no release check).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nstead of restarting forever
Ports the still-relevant piece of pingdotgg#3623: a backend that spawns fine but
keeps exiting before it ever answers a readiness probe previously
restarted silently forever (500ms-10s backoff, no cap, no surfacing).
After 5 consecutive never-ready exits the failure now surfaces through
onPreflightFailed — dialog + Windows fallback on the primary, inline
Connections error on the WSL secondary — exactly like exhausted preflight
and readiness failures.
The counter is scoped strictly to post-spawn exits: pre-spawn preflight
retries have their own counter, a crash after the backend was ready
resets the streak (an established backend crashing is a different failure
than one that never comes up), and the budget refreshes on ready, on
external stop, and on a fresh manual start so one cap firing does not
permanently remove the retry budget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 10, 2026
Comment threadapps/desktop/src/backend/DesktopBackendManager.ts Outdated

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 02343e8. Configure here.

Comment threadapps/desktop/src/backend/DesktopBackendManager.ts
…r-ready exit cap
A readiness-timeout termination closes the run scope, and the resulting
child exit previously ALSO incremented exitFailureAttempt — every
readiness timeout double-counted as a crash, so after a readiness-cap
recovery the leftover streak could fire a second, misleading "exited N
times" dialog just two real failures later. The readiness kill now marks
the run stopRequested before closing its scope, which both excludes it
from the exit cap and routes finalize to discardSession (the failure log
was already captured via persistFailureSnapshot). Regression test drives
five readiness cycles with a retrying onPreflightFailed and asserts the
exit cap never fires.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Jgratton24
Jgratton24force-pushed the fix/update-apply-wsl-handshake branch from 8e4c156 to 61c09f0CompareAugust 10, 2026 13:54
Comment threadapps/desktop/src/backend/DesktopBackendPool.ts Outdated
Jgratton24and others added 2 commits August 10, 2026 10:06
…SL-primary condition
resolvePrimary only selects the WSL backend when BOTH wslOnly and
wslBackendEnabled are set (wslRequested), but the non-fatal surfacing
branch checked wslOnly alone. With wslOnly persisted true while
wslBackendEnabled is false, a Windows primary exhausting readiness
retries took the WSL branch: a misleading "WSL backend is still
unavailable" dialog, an unrelated in-memory WSL settings mutation, and
one wasted retry cycle before the stop branch finally fired. The guard
now mirrors resolvePrimary's own condition, so a Windows primary
surfaces the generic backend-unavailable dialog and stops immediately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live testing on a real Windows+WSL host (WSL 2.7, mirrored) showed that
plain Linux fds and mmaps through 9p/DrvFs do NOT block Windows-side file
replacement, while a Windows-side handle does (the backend child runs as
the app executable and reads through app.asar). Comments now describe the
WSL release check as the defensive guarantee it is, with the verified stop
of Windows backend children as the confirmed-mechanism fix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ensureWindowsPathReleased spawned the holder scan as the default WSL
user. find_holders inspects /proc/<pid>/{cwd,exe,fd,maps}, which the
kernel exposes only to the process owner and root, so the unprivileged
scan silently got EACCES for any process it did not own. A root- or
other-user-owned process holding the install directory was therefore
invisible: the check returned RELEASED without ever seeing (let alone
killing) it, defeating the guard in exactly the case it exists for.
Spawn the probe as `wsl -u root` (WSL grants root with no password,
gated by the Windows host) so the scan sees every holder and its kills
land on holders it does not own. find_holders is already scoped to
processes that reference the install dir, so this cannot reach
unrelated processes.
Verified on Windows 11 + WSL2 (Ubuntu, mirrored): a root-owned holder
with cwd in the install dir that the old code left untouched is now
detected and SIGTERM-cleared before the installer handoff.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@SunkenInTimeChatGPT Codex Connector

Copy link
Copy Markdown
Contributor

Current head 249a6d93da1c094c9fe2f94af989a6377dcbe6cb is not merge-ready because the required Check job is red on formatting.

The failing job reports formatting issues in:

  • DesktopInstallIntegrity.ts
  • DesktopBackendConfiguration.test.ts / .ts
  • DesktopBackendManager.test.ts / .ts
  • DesktopUpdates.test.ts / .ts

Please run the repository formatter and push the result. I reproduced the functional focused suite on Windows at 126/127 passing; the one failure is the pre-existing Windows slash assertion in the external resource-monitor test, and both desktop and script-scope typechecks pass.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

2 participants

@Jgratton24@SunkenInTime