fix(packaging): workaround yauzl/Node 26 hang + fix API bootstrap contract

Packaging was silently hanging forever because yauzl 2.10.0 read streams
emit no data events under Node 26, causing extract-zip to block indefinitely
inside @electron/packager 20. Fix: postinstall script patches
@electron/packager/dist/unzip.js to use bsdtar (libarchive) instead.
bsdtar was chosen over 7z because 7z refuses chained symlinks in macOS
.app framework bundles. Both package:linux and package:mac now produce
correct output.

Also corrects the V3 API bootstrap contract in api_client.ts:
- SearchQuery body was wrapped in an extra {search_query: ...} layer — removed
- x-no-account-id header standardised to 'bypass'
- Redundant x-no-account-id removed from file download headers
- Smoke test rewritten to validate the real two-step bootstrap path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-11 16:48:15 -04:00
parent 36aed19169
commit bab08cd8a7
14 changed files with 797 additions and 1741 deletions

View File

@@ -0,0 +1,52 @@
# Native App Agent Task List
> Use this file to track steps for complex features or bug fixes.
> **Status:** Stable - ongoing development.
## Current Investigation
- This started as an API contract review for the native Electron bootstrap path and expanded into a packaging/runtime issue after the deploy step stopped producing bundles.
- We now know the API side was not the root cause. The bootstrap request shape in `src/main/api_client.ts` was wrong and has been corrected.
- The packaging blocker has been diagnosed and fixed (see below).
## What Was Fixed
- Updated the native bootstrap flow to use the direct `site_domain/search` request body expected by API V3.
- Standardized the account-bypass header to `x-no-account-id: bypass` where that narrow bypass is intended.
- Removed a redundant `x-no-account-id` header from file download calls.
- Rewrote the device lookup smoke test so it validates the real two-step bootstrap path end to end.
- Upgraded Electron from 34.x to 42.0.1.
- Replaced deprecated `electron-packager` with `@electron/packager` 20.0.0.
- Added a `package:linux` smoke test path so packaging failures can be isolated from macOS-specific behavior.
- **Fixed packaging hang on Node 26:** `yauzl` 2.10.0 (used by `extract-zip` in `@electron/packager`) emits no `data` events on Node 26 streams, causing zip extraction to hang indefinitely. Fix: patched `@electron/packager/dist/unzip.js` to use `7z` (system binary) instead of `extract-zip`. Patch is re-applied on every `npm install` via the `postinstall` script at `scripts/patch-packager-unzip.js`.
## Verified So Far
- `npm run dev` works once the Electron binary is present locally.
- Manual Electron cache extraction restored a runnable checkout on this machine.
- API validation confirmed the backend responds correctly for:
- `event_device/{id}` lookup
- `site_domain/search?limit=1` with the direct `SearchQuery` body
- The returned `site_domain.account_id` matches the device account context in the verified bootstrap flow.
- The SvelteKit frontend bootstrap path already follows the correct API contract and does not need the same fix.
- **`npm run package:linux` now produces `builds/aether_launcher-linux-x64/`** with a complete bundle (confirmed 2026-05-11).
- **`npm run package:mac` now produces `builds/aether_launcher-darwin-x64/` and `builds/aether_launcher-darwin-arm64/`** with `aether_launcher.app` inside each (confirmed 2026-05-11). Initial fix used `7z` but it refused chained symlinks inside macOS framework bundles; switched to `bsdtar` (libarchive) which handles both Linux and macOS zips correctly.
- `deploy/deploy.sh` output directory names (`aether_launcher-darwin-x64`, `aether_launcher-darwin-arm64`) match packager output — no script changes needed.
## Remaining Items
1. Test that the packaged Linux binary runs end-to-end against the dev API.
2. Document that `bsdtar` (libarchive) must be present on the build host — new build-time dependency. On Arch: `sudo pacman -S libarchive`.
## Root Cause Summary (Packaging Hang)
- **Tool chain:** Node 26.1.0 + `@electron/packager` 20.0.0 + `extract-zip` 2.0.1 + `yauzl` 2.10.0
- **Symptom:** `npm run package:linux` exits 0 but produces no output. Debug log shows it starts extraction but never finishes.
- **Root cause:** `yauzl` opens a read stream for the first zip entry, but on Node 26, no `data` events are ever emitted on that stream. The `pipeline(readStream, writeStream)` call in `extract-zip` blocks forever.
- **Fix:** Replace the one-liner `extractElectronZip` function in `node_modules/@electron/packager/dist/unzip.js` with a `child_process.execSync` call to `7z x`. A `postinstall` npm script re-applies this patch after each `npm install`.
- **Build-time dependency:** `p7zip` (provides `/usr/bin/7z`) must be installed on the build host. On Arch: `pacman -S p7zip`.
## References
- Electron 42 release notes: https://www.electronjs.org/blog/electron-42-0
- Related Electron packaging discussion: https://github.com/aaddrick/claude-desktop-debian/pull/587
- Electron packaging/runtime change reference: https://github.com/electron/electron/pull/49328
- yauzl Node 26 stream issue: `yauzl` 2.10.0 uses legacy Node streams (streams1 style); Node 26 changed stream internal behavior so `openReadStream` returns a stream that never emits `data` without a proper pipeline consumer.
## Notes
- Was on Electron 34.
- The problem is not the backend API keys or the frontend site bootstrap flow.
- The packaging fix is a node_modules patch, not upstream. If `@electron/packager` or `extract-zip` releases a Node 26-compatible version, the `postinstall` script should be removed.