Skip to content

perf(desktop): run packaged WSL backend from Linux filesystem - #5042

Closed
ReyJ94 wants to merge 10 commits into
pingdotgg:mainfrom
ReyJ94:perf/wsl-native-runtime
Closed

perf(desktop): run packaged WSL backend from Linux filesystem#5042
ReyJ94 wants to merge 10 commits into
pingdotgg:mainfrom
ReyJ94:perf/wsl-native-runtime

Conversation

@ReyJ94

@ReyJ94 ReyJ94 commented Jul 30, 2026

Copy link
Copy Markdown

What this fixes

On Windows, T3 Code currently runs its WSL server from the Windows install folder. Starting the
server makes WSL read thousands of small files through /mnt/c, which can take longer than the
60-second startup limit. This is the cause of the slow starts reported in #4535.

How it works

The Windows build now includes one compressed copy of the server and a checksum. On the first WSL
start, T3 Code verifies that file and unpacks it into WSL's Linux filesystem. Later starts reuse the
same immutable sha256-<archive hash> root without reading the Windows install again. Different
app versions can remain live side by side without one launch replacing or deleting another's files.

The cache serializes concurrent starts of the same archive, stages into a temporary directory, and
atomically promotes only a complete tree. If the archive is missing, damaged, or cannot be unpacked,
T3 Code logs a warning and uses the existing Windows-mounted runtime instead, so WSL still starts.

Windows artifact builds now fail if the Linux node-pty prebuild was not supplied, rather than
emitting an installer whose WSL backend cannot start.

The WSL script runner also keeps login-profile setup while preventing .bash_logout from changing
the script's real exit code.

Fixes #4535.

Verification

  • 98 focused tests passed across WSL staging, backend fallback, and Windows artifact building
  • Cold extraction, warm reuse with the archive removed, immutable updates, corrupt-archive recovery,
    concurrent same-archive starts, and concurrent distinct archives are covered with real shell and
    tar execution
  • Targeted formatting and lint passed for all changed files
  • Desktop and scripts typechecks passed
  • A direct same-host script comparison measured 8.51s cold / 0.03s warm for this PR and 6.57s cold /
    less than 0.01s warm for fix(desktop): Cache Runtime locally on WSL Filesystem, dramatically improving launch times  #5769; the extra cold time here is the full archive SHA-256 verification
  • No UI changed, so screenshots and video do not apply

Attribution

The scripted WSL shell transport (bash -l -c "exec bash -s") and its .bash_logout regression
coverage were also developed in ReyJ94/t3code#3 by
@playb0t. That stacked work was submitted before the equivalent changes landed here; credit and
provenance are preserved through this link.

Generated with GPT-5.6 Sol using Codex in T3 Code.

Note

Run packaged WSL backend from the Linux native filesystem on first launch

  • On Windows, the desktop build now packages a compressed WSL runtime archive and SHA-256 sidecar into app resources via stageWslRuntimeArchive in build-desktop-artifact.ts.
  • At launch, runWslPreflight in DesktopBackendConfiguration.ts calls preparePackagedRuntime to extract the archive into the distro's native Linux filesystem, caching it across launches and refreshing on update.
  • The staging script in DesktopWslEnvironment.ts uses flock for serialization, validates the SHA-256, atomically promotes the extracted tree, and short-circuits on cache hits to avoid touching the Windows mount.
  • ensureNodePty now accepts a linuxRepoRoot directly instead of a Windows path, removing an internal windowsToWslPath conversion.
  • Behavioral Change: if staging fails, the backend falls back to the Windows-mounted repo root, so existing behavior is preserved but the happy path now runs entirely from the Linux filesystem.

Macroscope summarized b4e9621.


Note

Medium Risk
Touches packaged WSL startup, artifact layout, and preflight fallbacks; mitigated by mount fallback, content-addressed cache, and broad shell/integration tests, but regressions could still affect Windows WSL-only users or CI Windows builds.

Overview
Packaged Windows WSL starts now prefer a Linux-native copy of the server instead of reading thousands of files through /mnt/c. The installer ships wsl-runtime.tar.gz plus a SHA-256 sidecar; WSL preflight calls new preparePackagedRuntime to verify, extract, and cache under an immutable sha256-<hash> root, then launches node from that tree.

Happy-path behavior: cache hits skip the Windows mount; concurrent first starts serialize with flock and double-check the cache; dev checkouts still use the repo root via windowsToWslPath. If staging fails (bad/missing archive, tools missing, etc.), preflight logs a warning and falls back to the existing Windows-mounted app.asar.unpacked layout so WSL can still start.

Build/packaging: Windows artifacts run stageWslRuntimeArchive during staging, ship the archive via extraResources (excluded from the ASAR), and fail the build when --wsl-prebuild is missing instead of emitting a broken WSL installer. Shared @t3tools/shared/wslRuntimeArchive centralizes filenames and hash parsing.

WSL shell transport: scripts run as bash -l -c "exec bash -s" so login profile state is kept while ~/.bash_logout cannot mask the real exit code. ensureNodePty now takes a linuxRepoRoot directly rather than converting a Windows repo path inside the WSL layer.

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

@coderabbitai

coderabbitai Bot commented Jul 30, 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: 11095731-efa5-4f27-a9e0-38ac08e3c0a4

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-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Jul 30, 2026
Comment thread apps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
@macroscopeapp

macroscopeapp Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR adds a new WSL runtime staging system that extracts the backend to Linux's native filesystem on first launch for performance. The changes introduce significant new shell scripting, content-addressed caching, archive extraction, and lock-based synchronization - a substantial new capability warranting human review.

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

Comment thread apps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated
Comment thread apps/desktop/src/wsl/DesktopWslEnvironment.ts
Comment thread apps/desktop/src/wsl/DesktopWslEnvironment.ts Outdated

@cursor cursor Bot 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.

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6b236d2. Configure here.

Comment thread scripts/build-desktop-artifact.ts
@ReyJ94
ReyJ94 force-pushed the perf/wsl-native-runtime branch from 6b236d2 to 04816f8 Compare August 11, 2026 17:27
@SunkenInTime

Copy link
Copy Markdown
Contributor

I found a correctness race in the mutable current cache at 3aaab8e.

The flock correctly serializes extraction/promotion, but every archive still returns the same path:

/home/t3review/.cache/t3code/desktop-wsl-runtime/current

I reproduced this on Ubuntu/WSL with the PR's actual generated buildPackagedRuntimeStageScript and two valid minimal runtime archives (different SHA-256 hashes, each containing the required server entry and Effect package):

A completed: 833.569 ms -> runtimeRoot:.../current
B completed: 871.645 ms -> runtimeRoot:.../current
final current entry: version B
cache version directories: current

The sequential form is deterministic too: after A stages and returns .../current, staging B makes A's previously returned path read B's code immediately. Because the lock is released before the caller launches Node, a second version can replace current in that gap, causing the first caller to launch the second version's runtime. If the first backend is already running, promotion renames and immediately deletes its old tree, which can also break later/lazy module loads.

The desktop single-instance lock reduces how often this can happen, but it doesn't make the staging helper's cross-process/version contract safe for relaunch/update or side-by-side builds.

Could you make the returned runtime root content-addressed by version/arch/archive hash (and prune old roots separately) rather than returning a mutable current alias? That keeps each caller pinned to the archive it validated and extracted.

@ReyJ94

ReyJ94 commented Aug 13, 2026

Copy link
Copy Markdown
Author

Sol here — I reproduced the mutable-current race and addressed it in 29f1958e.

The prepared runtime root is now content-addressed as sha256-<archive hash>. A launch for archive B no longer replaces or deletes the root previously returned to a still-running launch for archive A. Extraction happens in a unique staging directory; promotion only replaces an incomplete/corrupt directory for that exact hash. Concurrent callers for the same hash still serialize and extract once, while distinct archives can stage concurrently into distinct immutable roots.

I deliberately did not add eager "current + previous" pruning here. This staging boundary has no lease/liveness information for already-running backends, so pruning another hash could recreate the same lazy-load failure under a different policy. Cleanup should be a separate lease-aware or age-based owner rather than part of promotion.

I added deterministic sequential and concurrent distinct-archive regressions, and retained the same-hash concurrency coverage. The focused affected suites pass: 98/98, with targeted format/lint plus desktop and scripts typechecks also passing.

The related Windows packaging footgun raised on #5769 is separately fixed in 0411cf9f: a Windows artifact now fails when its Linux node-pty prebuild is missing instead of shipping a WSL backend that restart-loops.

— GPT-5.6 Sol (coding agent, posting through @ReyJ94's account)

@CDVolvik CDVolvik 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.

Re-verified on Linux (WSL2 Ubuntu, Node 24) against current main.

Branch as submitted, the two desktop suites this touches:

Test Files  2 passed (2)
     Tests  65 passed (65)

The question I could not answer on an earlier pass was whether these tests actually pin the new behaviour or just happen to pass. They do. Swapping only DesktopBackendConfiguration.ts and DesktopWslEnvironment.ts for the current main copies, keeping this PR's tests:

× resolveWsl pins a default-tracking run to the concrete default distro
  AssertionError: expected [ 'Ubuntu', 'Ubuntu' ] to deeply equal [ 'Ubuntu', 'Ubuntu', 'Ubuntu' ]
× resolveWsl falls back to the mounted runtime when native staging fails
  AssertionError: expected [ Array(1) ] to deeply equal [ '/mnt/c/t3code' ]
× resolveWsl preserves inherited PATH with quote-sensitive values as separate args

Three targeted assertion failures, each naming the behaviour it lost, rather than the whole file failing to load. That distinction matters here because an import-level failure would prove only that the module changed shape, not that anything is covered. These are real assertions on real behaviour, including the /mnt/c fallback path, which is the one worth having pinned given what the PR is for.

Related, since the threads are drifting apart: #6129 raises DEFAULT_BACKEND_READINESS_TIMEOUT from 1 to 3 minutes to absorb a WSL backend on /mnt/c taking 60-98s to answer. This PR and #5769 both remove that cost rather than budget for it. Whichever way maintainers want to go, it is worth deciding deliberately, because landing the timeout bump and then landing this leaves a tripled global readiness timeout with no remaining justification and nothing to prompt putting it back.

@ReyJ94

ReyJ94 commented Aug 15, 2026

Copy link
Copy Markdown
Author

Closing this as superseded by #6169 and the bundled server topology now on main.

I benchmarked the current materialized server payload in WSL (1,223 files) from both ext4 and /mnt/c. Fresh backend readiness was 5.85–7.49s on ext4 versus 6.23–6.50s on DrvFS; repeated --version startup was 1.31–1.63s versus 2.08–2.48s. The former 60–98s small-file penalty is gone, so this PR's extra runtime archive, hashing, extraction, locking, fallback, and cache lifecycle no longer earn their complexity.

The cache-race and missing-prebuild findings on this branch were real, but after #6169 this architecture is no longer the right direction.

— GPT-5.6 Sol (coding agent, posting through @ReyJ94's account)

@ReyJ94 ReyJ94 closed this Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

3 participants