Skip to content

fix(ci): repair the macOS Intel and Windows artifact builds - #61

Merged
tnunamak merged 3 commits into
mainfrom
fix/release-artifact-builds
Sep 6, 2026
Merged

fix(ci): repair the macOS Intel and Windows artifact builds#61
tnunamak merged 3 commits into
mainfrom
fix/release-artifact-builds

Conversation

@tnunamak

@tnunamak tnunamak commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Repairs the Windows and macOS legs of Release manual-install artifacts. All four build
jobs now pass
(run 34038355147). Three commits.

Windows turned out not to be one bug but a chain of four, each hidden behind the previous
one in the same npm ci → build sequence:

# Failure Fix
1 node-gyp cannot parse the VS 2026 \18\ path, so better-sqlite3 won't build pin runner to windows-2022 (here)
2 spawnSync patchright ENOENT landed on main via #55
3 spawn npx ENOENT in vendored read-core/mcp-server build scripts shell: platform === "win32" (here)
4 error[E0432] — Unix-only kill_process_group import not #[cfg(unix)]-gated gate the import (here)

macOS is a separate, unrelated fix: the Intel job intermittently hung ~24m in Tauri's
bundle_dmg.sh and died with an orphaned diskimages-help. That DMG is deleted and rebuilt
by scripts/create-macos-dmg.mjs, which retries on hdiutil resource busy — so the job
was dying inside discarded work using the fragile implementation. --bundles app skips it.

#48 is subsumed by this PR and can be closed — see the Round 2 comment. Detailed
evidence for every claim, including two corrected premises, is in the comments below.


Original PR description (written before #55 merged; superseded in places)

Summary

Two release-artifact build jobs were investigated: build (windows-…) and
build (macos-15-intel, …). They fail for unrelated reasons, and only one of them is
actually broken on every run.

Windows — a hard break, red on 9 of the last 9 runs. npm ci fails while building
better-sqlite3, this repo's only Windows-native dependency. The windows-latest image
now installs Visual Studio under a bare \18\ path (VS 2026 naming). node-gyp 11.5.0
expects a year — 2017/2019/2022 — reads the version as undefined, and gives up even
though a usable toolchain is present:

gyp ERR! find VS unknown version "undefined" found at "C:\Program Files\Microsoft Visual Studio\18\Enterprise"
gyp ERR! find VS could not find a version of Visual Studio 2017 or newer to use
gyp ERR! cwd D:\a\data-connect\data-connect\node_modules\better-sqlite3
gyp ERR! node-gyp -v v11.5.0

Pointing npm at a newer node-gyp does not fix this. npm ci runs its automatic native
rebuilds through its own bundled copy and ignores npm_config_node_gyp (npm/cli#2839);
the stack trace confirms the failing binary is
…\node\22.23.1\x64\node_modules\npm\node_modules\node-gyp\bin\node-gyp.js. The job is
pinned to windows-2022, whose image predates the rename, with a dated TODO for unpinning
once actions/setup-node's Node 22 LTS ships npm ≥ 11.6.3 (node-gyp ≥ 12.1.0, which added
VS 2026 detection). Fixes #47.

macOS — an intermittent hang, not a constant failure. On run 33818382290 the Intel job
spent nearly 24 minutes inside Tauri's bundle_dmg.sh before failing, leaving an orphaned
diskimages-help process — a wedged hdiutil:

23:52:16Z      Running bundle_dmg.sh
00:16:05Z failed to bundle project: error running bundle_dmg.sh: `failed to run …/bundle_dmg.sh`
00:16:11Z Terminate orphan process: pid (65541) (diskimages-help)

The load-bearing detail is that this work is thrown away. Finalize platform bundles
deletes every DMG Tauri just produced and rebuilds it with scripts/create-macos-dmg.mjs,
which exists precisely because hdiutil is flaky — it retries and detaches stale devices
on resource busy. The job was dying inside a step whose output is discarded, using the
fragile implementation, before ever reaching the robust one. Restricting the macOS build
to --bundles app skips bundle_dmg.sh entirely.

Both macOS legs run that script — the ARM job reached Running bundle_dmg.sh on the same
run and simply got lucky — so the flag is applied to both, not only Intel.

Two corrections to the original diagnosis

macos-15-intel is not retired. The starting assumption was that the label had been
retired and needed replacing. It has not: it schedules real runners and passed on
2026-09-04 ({"conclusion":"success","labels":["macos-15-intel"],"runner":"GitHub Actions 1001845299"}).
A retired label does not schedule, it queues until timeout. Replacing or dropping it would
have removed a working target and broken releases, since publish requires
manual-install-macos-x64.

The Intel job was not failing on every PR. It is green in 11 of the last 12 runs. It
is worth fixing because this removes the cause rather than the symptom, but it is a flake
fix. A green run here does not by itself prove it — the job passes ~90% of the time
anyway. The structural evidence is that bundle_dmg.sh no longer runs at all, so the step
that hung cannot hang; confirm by checking the macOS logs no longer contain
Running bundle_dmg.sh.

Overlap with #48 — please read before merging

The Windows change here is byte-for-byte identical to open PR #48, which is already
proven green (run 33819168800: all four build jobs pass, including windows-2022 and
macos-15-intel). What #48 does not contain is the macOS --bundles app change; its
release.yml diff touches only the Windows matrix entry.

Reasonable options:

Either is defensible, but both PRs currently edit the same matrix entry, so one of them
should give way rather than both merging.

Note on the third change

mkdir -p "$bundle_root/dmg" is not cosmetic. With --bundles app, Tauri no longer
creates the dmg/ directory, so the cleanup find that follows would exit non-zero and
abort the step under set -euo pipefail.

Verification

app was confirmed to be a real bundle value rather than assumed. In tauri-cli-v2.11.4,
--bundles takes BundleFormat, a newtype over PackageType whose FromStr delegates to
PackageType::from_short_name, which maps "app" => PackageType::MacOsBundle. ValueEnum
exposes all of PackageType::all() and only hides Nsis/Updater from help output
without rejecting them — so the shorter list shown by tauri build --help on Linux is a
display artifact, not a validation limit. app is also the .app bundle the DMG step
already depends on, so nothing the workflow consumes is lost.

Assisted-by: AI

@tnunamak

tnunamak commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

CI results (run 34031480058)

Job Result
build (macos-15-intel, …) success
build (macos-15, …) (ARM) success
build (ubuntu-22.04, …) success
build (windows-2022, …) failure — new error, see below

macOS: fixed, with structural proof

A green tick alone would be weak evidence here, since the Intel job already passed ~90% of
the time. The stronger proof is that the step which used to hang no longer runs at all:

$ gh api .../jobs/101481677412/logs | grep -c "Running bundle_dmg.sh"   # ARM
0
$ gh api .../jobs/101481677189/logs | grep -c "Running bundle_dmg.sh"   # Intel
0

And the DMG is still produced, by the retry-capable script:

Job Build unsigned Tauri app Finalize platform bundles
ARM success, 8m 2s success, 49s
Intel success, 24m 37s success, 2m 11s

Previously Intel spent ~24m inside bundle_dmg.sh and then failed. Now that time is the
x86_64 release compile, and Finalize platform bundles builds the DMG in 2m 11s.
verify-bundled-personal-server.mjs runs there, so the artifact is verified.

Windows: the pin worked, and uncovered the next bug

node-gyp and better-sqlite3 no longer appear in the log — the pin did its job and npm ci
got further than it ever has. It now fails on a separate, latent Windows bug:

npm error path D:\a\data-connect\data-connect\node_modules\@pdpp\polyfill-connectors
npm error command C:\Windows\system32\cmd.exe /d /s /c node ./scripts/install-patchright-browser.mjs
npm error spawnSync patchright ENOENT

spawnSync("patchright", …) without shell: true cannot find the .cmd shim npm writes
for package binaries on Windows.

This is not fixable from this branch. The failing package is the root dependency, which
package-lock.json resolves to a vendored tarball:

node_modules/@pdpp/polyfill-connectors -> file:reference-implementation/vendor/pdpp-polyfill-connectors-0.0.1.tgz

That is precisely one of the files #48 replaces (Bin 2497106 -> 2498092 bytes), re-vendored
after the upstream shell: true fix in PDP-Connect/data-connectors#60 — along with the
same problem in read-core/mcp-server and a #[cfg(unix)] import in ref_server.rs.

So the Windows leg needs the runner pin and those source fixes. #48 has both; this branch
has only the pin.

Suggested sequencing

Merge #48 first (pin + source fixes, already green on run 33819168800). This PR then reduces
to the macOS --bundles app change, which #48 does not contain. Merging this one alone would
leave Windows red on patchright ENOENT.

Assisted-by: AI

The Windows release job has failed on every pull request since
better-sqlite3 entered the dependency tree. node-gyp cannot parse the
Visual Studio install path on the current windows-latest image, which is
now a bare "\18\" (VS 2026 naming), and fails with unknown version
"undefined". npm ci performs native rebuilds with npm's own bundled
node-gyp and ignores npm_config_node_gyp, so pointing at a newer node-gyp
does not help. Pin the job to windows-2022, whose image predates the
rename, with a dated TODO recording when the pin can be lifted.

The macOS jobs let Tauri run bundle_dmg.sh even though the DMG it
produces is deleted and rebuilt by scripts/create-macos-dmg.mjs in
"Finalize platform bundles". That duplicate work is not free: on run
33818382290 it hung on hdiutil for 24 minutes and failed the
macos-15-intel job, leaving an orphaned diskimages-help process. Build
only the app bundle on macOS so the redundant script never runs. Both
macOS legs execute it, so both are covered.

Restricting the bundle means Tauri no longer creates the dmg directory,
so create it before the cleanup find, which would otherwise abort under
set -euo pipefail.

Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
…ipts

With the runner pinned to windows-2022, npm ci gets past better-sqlite3
and now fails in the vendored read-core build script:

  npm error path ...\reference-implementation\vendor\read-core
  npm error Error: spawn npx ENOENT
  npm error   spawnargs: [ 'tsc', '--project', 'tsconfig.build.json' ],

execFile resolves a bare command name by PATH lookup alone. Without a
shell there is no PATHEXT resolution, so on Windows it never finds the
npx.cmd shim npm writes for bin entries. Both read-core and mcp-server
run this script from a prepare hook, so npm ci executes them on every
install; read-core failed first only because it is installed first.

Pass shell: platform === "win32" so the lookup goes through cmd.exe
there and stays a direct spawn everywhere else. This matches the
existing cross-platform fix on main for these same scripts
(068bc81, which stopped them shelling out to pnpm).

Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
With npm ci fixed, the Windows Tauri build reaches rustc and fails:

  error[E0432]: unresolved import `super::server::kill_process_group`
   --> src\commands\ref_server.rs:35:5
  note: found an item that was configured out
   --> src\commands\server.rs:13:8

kill_process_group is defined under #[cfg(unix)] in server.rs, and all
four call sites in ref_server.rs are already inside #[cfg(unix)] blocks.
Only the import was ungated, so the module failed to resolve on Windows
and nowhere else.

Verified by mutation on a reduced model of the same cfg structure:
with the gate the non-unix configuration compiles; removing it
reproduces E0432 exactly. Unix keeps the import in use, so no
unused-import warning appears there.

Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
@tnunamak

tnunamak commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Round 2 — rebased onto #55; all four build jobs now pass

main at a51f1b17c carries #55's re-vendored polyfill-connectors tarball, so the
patchright ENOENT reported above is resolved. The Windows job then failed twice more, for
two further reasons. Both are fixed here.

The useful finding: Windows was never one bug. It was a chain of four, each hidden
behind the previous one, all inside the same npm ci → build sequence.

# Failure Where Status
1 gyp ERR! find VS unknown version "undefined" npm ci fixed here — windows-2022 pin
2 spawnSync patchright ENOENT npm ci postinstall fixed on main by #55
3 spawn npx ENOENT npm ci prepare hook fixed here (1cc502fa2)
4 error[E0432]: unresolved import rustc fixed here (2ede4a9af)

3 — spawn npx ENOENT in vendored build scripts

npm error path D:\a\data-connect\data-connect\reference-implementation\vendor\read-core
npm error Error: spawn npx ENOENT
npm error   spawnargs: [ 'tsc', '--project', 'tsconfig.build.json' ],

execFile resolves a bare command name by PATH lookup alone; with no shell there is no
PATHEXT resolution, so Windows never finds the npx.cmd shim npm writes for bin entries.
read-core and mcp-server both call execFile("npx", …) in scripts/build.ts and both
declare "prepare": "npm run build", so npm ci runs them on every install. read-core
failed first only because it installs first; mcp-server was next.

Fixed with shell: platform === "win32" on both calls. A sweep of execFile/spawn across
reference-implementation/vendor/*/scripts/ confirmed these are the only two on the npm ci
path — cli needs no change because its build is an npm script string, which npm already
runs through a shell.

Editing these vendored sources directly follows precedent on main: 068bc8180 ("stop
vendored packages' build scripts from shelling out to pnpm") changes the same two files for
the same cross-platform reason. vendor/README.md's re-vendoring policy covers tarballs;
these are tracked source directories.

4 — Unix-only import not gated for Windows

With npm ci green the job reached rustc for the first time:

error[E0432]: unresolved import `super::server::kill_process_group`
 --> src\commands\ref_server.rs:35:5
note: found an item that was configured out
 --> src\commands\server.rs:13:8
12  | #[cfg(unix)]

kill_process_group is #[cfg(unix)], and all four call sites in ref_server.rs (384, 395,
470, 480) were already gated — checked individually. Only the import was not. One-line fix.

Verified by mutation rather than reasoning, since the Windows target isn't installed locally:
on a reduced model with the same cfg structure, the gate makes the non-unix configuration
compile, and removing it reproduces E0432 exactly. Unix still uses the import, so no
unused_imports warning.

Check results — run 34038355147

All green:

SUCCESS | build (macos-15-intel, …)          SUCCESS | build (macos-15, …)
SUCCESS | build (ubuntu-22.04, …)            SUCCESS | build (windows-2022, …)
SUCCESS | reference-implementation gate      SUCCESS | test reference implementation
SUCCESS | typecheck reference implementation SUCCESS | pack-install-run
SUCCESS | check-spdx-headers                 SUCCESS | classify reference-impacting changes
SUCCESS | DCO                                SKIPPED | publish (release-only)

The macOS proof still holds — the step that used to hang never executes:
grep -c "Running bundle_dmg.sh" returns 0 for both macOS jobs (101500460657,
101500460791).

#48 is now subsumed

The earlier recommendation to merge #48 first no longer applies. With #55 landed and the two
source fixes here, this PR is green on its own and contains everything #48 does — the pin,
the read-core/mcp-server shell fix, and the ref_server.rs cfg gate — plus the macOS
--bundles app change #48 never had. #48 can be closed as superseded.

Flagged, not fixed

Windows logs a non-fatal [resolve-connectors] ERROR: Connector lock drift detected during
npm ci. It self-recovers on the next line (installed 14 locked connector(s)) and does not
block the build. It appears on Windows only — 0 occurrences in the passing Linux job —
suggesting a path or line-ending nuance in the lock comparison. Worth its own issue; fixing
it here would widen this PR beyond one change.

Assisted-by: AI

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

Labels

None yet

Projects

None yet

1 participant