Skip to content

fix(mac): terminal posix_spawn ENOENT; run package commands on the Electron Helper and drop bundled Node - #646

Merged
yaojin3616 merged 11 commits into
mainfrom
fix/node-pty-unpacked-helper
Oct 3, 2026
Merged

yaojin3616 merged 11 commits into
mainfrom
fix/node-pty-unpacked-helper

Conversation

@yaojin3616

@yaojin3616 yaojin3616 commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Two related macOS runtime fixes, from packaged Harness and package commands running from app.asar.unpacked on the Electron runtime, plus a generation-installer fix found while verifying them.

1. Sidebar terminal: posix_spawn failed: No such file or directory

node-pty starts the shell through spawn-helper. It derives the helper path with replace('app.asar', 'app.asar.unpacked'), so a path that is already unpacked became app.asar.unpacked.unpacked/.../spawn-helper. Since #570, packaged Harness loads dependencies from that physical directory.

  • patches/node-pty+1.2.0-beta.15.patch skips paths already followed by .unpacked. 1.2.0-beta.15 is the newest beta; remove the patch once upstream fixes it.
  • Windows is unaffected: it has no spawn-helper. A scan of the packaged app found no other dependency that rewrites app.asar paths.

2. One Node runtime on macOS: Electron Helper; bundled Node removed

macOS Harness already ran in an Electron utility process. Profile repair, market baseline, generation installs and the .desktop-bin node/pnpm shims still used a bundled standalone Node 24.9.0, so two Node runtimes installed into one Profile. The main-process shim writer and the Harness-side shim writer also disagreed with each other.

  • bundledNodePath() → electronNodeExecutable(): the app's Helper on macOS, the main executable elsewhere. Package commands and shims always set ELECTRON_RUN_AS_NODE=1. The generation installer now injects it under any Electron runtime, not only on Windows. Without it the Helper boots a full GUI app; during testing this showed up as a bogus "gpu-crash" report.
  • Main-process generation installs (plugin upgrade, legacy migration) now go through packageCommandEnvironment(), which rewrites the shims and puts them on PATH. pnpm adds only its runtime's directory for lifecycle scripts, and the Electron runtime has no node there, so a dependency's node … install script failed with node: command not found. Rewriting the shims also replaces ones an upgraded install left pointing at the removed Node.
  • Linux development launches Harness through Electron Node mode as well.
  • Removed the node dependency (lockfile: node, node-bin-setup) and the win.files exclusion for it. verify-target and the afterPack runtime check use the Electron executable / Helper instead. Docs updated.

3. Generation installer hoisted an @deepseek-ai/* plugin out of its own generation

The host-singleton patterns cover the whole @deepseek-ai scope. Installing a plugin from that scope (seen with @deepseek-ai/dsh-subagent-claude-code during legacy migration) therefore removed the plugin itself from the generation. Migration then failed with ENOENT on its package.json and rolled the Profile back; peer validation would also have reported it as a private host singleton. The generation walk used by hoisting and by validation now exempts only the top-level plugin package. Nested copies of the same name and every other host package are still removed. Regression: test/generation-installer.test.ts (fails without the fix).

Checks

  • Clean npm ci (node-pty patch applied, no node_modules/node); npm test 179 files / 1600 tests; npm run typecheck; git diff --check; node scripts/verify-target.mjs darwin arm64 (Electron 43.0.0 / Node 24.17.0 loads the Harness native loader)
  • Regression tests that fail without the fix:
    • node-pty loaded from app.asar.unpacked spawns a PTY
    • package commands plus both shims on the real Electron Helper, with no ELECTRON_RUN_AS_NODE in the base env (hangs without the fix)
    • stale shims naming a removed Node are rewritten
    • a dependency postinstall that runs node succeeds with a Node-free base PATH
  • Unsigned package:dev:dir (arm64):
    • no node_modules/node; app.asar.unpacked 661 MB → 549 MB; the afterPack check ran on the Helper
    • node-pty from app.asar.unpacked starts a login shell
    • the packaged installer, driven from an Electron main process with the Helper, published a real generation; with the old win32-only condition it failed
  • Upgrade simulation: packaged build on a temp userData seeded with shims naming the removed bundled Node.
    • The shims were rewritten to the Helper.
    • The main-process dsh CLI and pnpm ran.
    • A migrated plugin generation installed via the Helper.
    • Two failures unrelated to the runtime were observed. The installer hoisting an @deepseek-ai/* plugin out of its own generation is fixed in section 3. dshmarket@1.65.1 declaring peers that do not include dsh 0.2.0-rc.2 needs a market release and is not addressed here.

Not verified: signed/notarized build, x64 mac, Windows packaging and installer (CI), market install and upgrade through the real UI.

🤖 Generated with Claude Code

yaojin3616 and others added 2 commits October 1, 2026 10:02
Packaged Harness loads its dependencies from the physical
app.asar.unpacked directory (runtimePackageRoot, since #570). node-pty
derives spawn-helper by replacing "app.asar" with "app.asar.unpacked",
which turned the already-unpacked path into app.asar.unpacked.unpacked,
so every macOS terminal failed with "posix_spawn failed: No such file or
directory".

Patch node-pty 1.2.0-beta.15 to skip paths that are already unpacked.
Windows does not use spawn-helper and is unaffected.

The patched-JS identifier check now also allows the CommonJS host
globals __dirname and clearImmediate, as node-pty is the first CJS patch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed Node

macOS Harness already runs in an Electron utility process, but Profile
repair, market baseline, generation installs and the .desktop-bin
node/pnpm shims still used a bundled standalone Node 24.9.0, so two Node
runtimes installed into one Profile and the main-process and Harness-side
shim writers disagreed.

- bundledNodePath() now returns the app's Helper on macOS (main
  executable elsewhere); package commands and shims always set
  ELECTRON_RUN_AS_NODE=1, and the generation installer injects it under any
  Electron runtime instead of only on Windows. Without it the Helper boots
  a full GUI app (seen as a bogus gpu-crash report).
- Main-process generation installs (plugin upgrade, legacy migration) now
  rewrite the .desktop-bin shims first and put them on PATH: pnpm only adds
  its runtime's directory for lifecycle scripts, and the Electron runtime
  has no `node` there; rewriting also replaces shims an upgraded install
  left pointing at the removed Node.
- Linux development launches Harness through Electron Node mode too.
- Remove the `node` dependency; verify-target and the afterPack runtime
  check use the Electron executable / Helper instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@yaojin3616 yaojin3616 changed the title fix(mac): sidebar terminal fails with posix_spawn ENOENT in packaged app fix(mac): terminal posix_spawn ENOENT; run package commands on the Electron Helper and drop bundled Node Oct 2, 2026
yaojin3616 and others added 2 commits October 1, 2026 20:14
The host-singleton patterns cover the whole @deepseek-ai scope, so
installing a plugin from that scope (e.g. @deepseek-ai/dsh-subagent-claude-code)
hoisted the plugin itself out of its generation. Legacy migration then
failed with ENOENT on its package.json and rolled the Profile back, and
peer validation would report it as a private host singleton.

Exempt only the generation's top-level plugin package from the singleton
walk used by both hoisting and validation; nested copies of the same name
and every other host package are still removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… files

Align the package layout with upstream deepseek-harness apps/desktop.
Since #646 every consumer of the bundled packages runs on the Electron
runtime (main, utility process, Helper in Node mode), which reads
app.asar, so unpacking all of node_modules bought nothing: about 21k
loose files that slowed installs and put "app.asar" in physical paths
for dependencies' path heuristics to trip over.

- asarUnpack keeps only native addons/libraries, spawn-helper, ripgrep,
  the LibreOffice engine and sherpa-onnx platform packages and the PPT
  runtime (macOS arm64: 1.6k files / 235 MB unpacked, was 21k / 549 MB).
- Remove runtimePackageRoot; the bundled runtime root is app.getAppPath().
- build/office-engine-resolution.mjs (adapted from upstream
  desktop-host office-engine.ts) resolves the LibreOffice engine package
  to app.asar.unpacked so the OS can spawn its executable; registered in
  harness-node-entry.
- Drop the node-pty patch: upstream node-pty maps app.asar to
  app.asar.unpacked itself, which is correct in this layout.
- afterPack (scripts/after-pack.cjs) now verifies the PPT runtime through
  app.asar and fails when a Mach-O/ELF/PE file is packed inline.
- Windows release smoke loads koffi and pnpm through app.asar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
yaojin3616 and others added 3 commits October 2, 2026 00:53
…g findings

With blocking compatibility findings the sidebar button reopened the
Safe Mode manager and returned. When the manager was already open the
click had no visible effect, while the manager's own restart button asked
for confirmation and exited.

Ask the same question as the manager's restart button (shared
safeModeExitConfirmation / safeModeBlockingGroupCount): "Exit anyway"
leaves Safe Mode, "Manage plugins" opens the manager. When the manager is
open and waiting, the exit is handed to its restart action so relaunch,
the unresolved-findings note and a fall-back into Safe Mode are handled in
one place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(packaging): load JavaScript from app.asar and unpack only native files
@yaojin3616
yaojin3616 merged commit 5877f45 into main Oct 3, 2026
4 checks passed
@yaojin3616
yaojin3616 deleted the fix/node-pty-unpacked-helper branch October 3, 2026 05:48
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.

1 participant