Skip to content

[Bug]: Windows installer writes 14,687 files to ship 20 native binaries, costing install time and ~6s of cold start #5876

Description

@tsouth89

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

Build, CI, or release tooling

Steps to reproduce

  1. pnpm dist:desktop:win on Windows 11 x64 (measured at main @ ba9c9ae; re-checked at 02f4ce5)
  2. Count the files in the NSIS payload:
    7z e release/T3-Code-*-x64.exe '$PLUGINSDIR/app-64.7z' then 7z l -slt app-64.7z
  3. Install the result, and time the first launch

Expected behavior

The installer writes roughly the number of files the app actually needs on disk, and a cold start is not dominated by module loading.

Actual behavior

The Windows installer writes 14,687 files. 13,875 of them are loose node_modules files under app.asar.unpacked. Only 20 are native .node binaries. For contrast, the entire Electron runtime — several hundred MB — is 22 files, because it stays inside the archive.

files
total written at install 14,687
app.asar.unpacked/node_modules 13,875
app.asar.unpacked/apps/server/dist 790
everything else, incl. the Electron runtime 22
genuinely native .node binaries 20

The cause is WINDOWS_ASAR_UNPACK in scripts/build-desktop-artifact.ts:

export const WINDOWS_ASAR_UNPACK = ["apps/server/dist/**", "**/node_modules/**"] as const;

That blanket unpack is load-bearing today: the CLI bundle externalizes its runtime dependencies, and the WSL backend launches plain wsl.exe -- node, which cannot read inside an asar. So every external dep has to be a real file.

This costs twice:

  • Install. NSIS install time tracks file count, not bytes.
  • Cold start. Each of those files is a separate open/stat/scan the first time the server runs after an install — exactly when the OS file cache is cold and the on-access scanner is not.

Inverting the bundler's rule — bundle everything except what genuinely cannot be inlined (native addons, the JS wrappers that dlopen them, and the Bun-only entry points resolving bun:*) — lets asarUnpack narrow to that set:

before after
files written at install 14,687 1,192 (−92%)
loose node_modules files 13,875 370
native .node binaries 20 20
installer size 145.0 MiB 138.9 MiB

Cold start improves by the same mechanism. Extracting each build's payload to a fresh directory (so the files have never been read) and booting the server, alternating run order between builds so cache and scanner state could not favour either one:

before after
server boot to Listening on 9,044ms / 10,160ms 3,667ms / 3,779ms
module load only (--version) 6,521 / 6,238 / 6,208ms 761 / 659 / 654ms

The main window is not created until the backend answers HTTP, so that ~6s comes straight off a cold launch.

Second, unrelated issue in the same area. installWindowsEnvironment in apps/desktop/src/shell/DesktopShellEnvironment.ts spawns PowerShell twice in series at offset 0 of desktop.startup, before anything else runs. The two probes are independent — only their results are combined. Traces over this code path measured them at 2718ms then 2066ms: the whole 4.8s startup span, of which desktop.bootstrap is ~30ms.

Why this is Windows-only: asarUnpack is gated to platform === "win". macOS and Linux use electron-builder's smart unpack and stay packed, so none of this is visible there.

Impact

Major degradation or frequent failure

Version or commit

main @ ba9c9ae (re-verified against 02f4ce5)

Environment

Windows 11 Enterprise 26220, x64, Node 24.18.0, pnpm 11.10.0, electron-builder win/nsis

Workaround

None from the user side; it's in the packaging config.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions