Skip to content

Suppress stray Windows Terminal window behind the standalone app#110

Merged
nedtwigg merged 6 commits into
mainfrom
fix-windows-console
May 28, 2026
Merged

Suppress stray Windows Terminal window behind the standalone app#110
nedtwigg merged 6 commits into
mainfrom
fix-windows-console

Conversation

@nedtwigg
Copy link
Copy Markdown
Member

Summary

  • Patch the bundled node.exe to PE subsystem 2 (Windows GUI) so the sidecar no longer triggers Windows Terminal's default-terminal handoff — that handoff was spawning a stray WT window titled with the node.exe path behind the Dormouse window on Win11.
  • Consolidate the Node.js pin into package.json's devEngines.runtime.version as the single source of truth: pnpm honors it locally (onFail: "download"), CI extracts it for actions/setup-node, build.rs verifies the bundled binary against it, and the supply-chain page reads the same field.
  • Defensively clear the read-only attribute on the bundled node.exe before patching — pnpm's content-addressable store leaves source binaries read-only on Windows, which fs::copy propagates to the destination and would otherwise break pnpm tauri build locally.
  • Validate the extracted pin in CI so a missing/malformed devEngines.runtime.version fails the right step with a clear message instead of becoming node-version: null downstream.
  • Disclose the byte flip in SECURITY.md so the supply-chain claim ("version disclosed provably equals what ships") still reads honestly — the bundled node.exe differs from the upstream archive at exactly the documented 2-byte field, version-equality is unchanged.

Test plan

  • Local pnpm tauri build on Windows — confirm no stray Windows Terminal window appears behind the standalone app at launch
  • CI build-standalone job goes green across Linux / macOS / Windows
  • node website/scripts/generate-deps.js from a clean checkout produces no diff
  • Security audit passes (no security-audit-failure issue opened against the new SECURITY.md wording)

🤖 Generated with Claude Code

nedtwigg and others added 6 commits May 28, 2026 09:58
Move the bundled Node.js version pin out of standalone/.node-version and
into the root package.json's devEngines.runtime block. pnpm 11 honors
this field natively (onFail: "download") so scripts run with the pinned
Node locally, eliminating the "wrong node on PATH" failure mode for
contributors. build.rs reads the same field to verify the bundled
binary, generate-deps.js reads it for the supply-chain disclosure, and
release.yml extracts it via jq to drive actions/setup-node — all from
one file.

Also drops the brittle cargo:rerun-if-changed on the resolved node
source path in build.rs: when a node version manager's symlink went
dangling (e.g. vfox's cache/current pointing at a deleted version),
cargo would keep replaying the stale error instead of re-running.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First `pnpm install` after adding devEngines.runtime materialized the
runtime in pnpm-lock.yaml as `node@runtime:22.22.3` with sha256-pinned
tarballs for every platform variant (aix/darwin/linux/win × cpu archs +
musl). This makes the bundled Node provably reproducible from the
lockfile alone, on top of build.rs's runtime check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DETACHED_PROCESS on the sidecar spawn was not actually preventing the
visible window — checking the live process tree shows Windows Terminal
(class CASCADIA_HOSTING_WINDOW_CLASS) hosting the sidecar with the title
set to the node.exe path. On Win11 with WT as the default terminal, the
DefTerm COM handoff fires for any console-subsystem (IMAGE_SUBSYSTEM_
WINDOWS_CUI = 3) child regardless of CREATE_NO_WINDOW / DETACHED_PROCESS:
those flags suppress the conhost window but not the WT activation.

Flip the bundled node.exe's PE Optional Header Subsystem field to 2
(IMAGE_SUBSYSTEM_WINDOWS_GUI) during the build script's post-copy step.
A GUI-subsystem binary is never auto-given a console, so the DefTerm path
has nothing to hand off and WT is never activated. Node.js itself reads
its stdio handles from STARTUPINFO and works identically under either
subsystem; smoke-tested with `node -e 'console.log(...)'` after patching.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fs::copy on Windows preserves FILE_ATTRIBUTE_READONLY from the source.
When the runtime comes from pnpm's content-addressable store (devEngines
onFail: "download"), the source node.exe is read-only, so the destination
inherits that and fs::write fails with "access denied" — breaking
`pnpm tauri build` on a local Windows workstation. Clear the attribute
defensively before writing the patched buffer back.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…json

`jq -r` on a missing field exits 0 with the literal output "null", which
silently propagated to actions/setup-node as `node-version: null` and
produced a confusing "Unable to find Node version" error a step later.
Validate the MAJOR.MINOR.PATCH shape at the extraction step so a missing
or malformed pin fails CI at the right place with a clear message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The build patches one byte of the bundled node.exe on Windows
(IMAGE_SUBSYSTEM_WINDOWS_CUI → IMAGE_SUBSYSTEM_WINDOWS_GUI) to suppress
Windows Terminal's default-terminal handoff. The version-equality claim
still holds — `node --version` runs before the patch — but the bundled
binary is no longer byte-identical to the upstream archive, so spell
that out for a supply-chain reader.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@nedtwigg nedtwigg merged commit 9c2e70f into main May 28, 2026
6 checks passed
@nedtwigg nedtwigg deleted the fix-windows-console branch May 28, 2026 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants