kimi-code/scripts/fix-node-pty-perms.mjs
qer 2d855b06a0 fix(terminal): make node-pty load and spawn in packaged + pnpm-dev builds
Two distinct PTY failures:
- 'Failed to load native module: pty.node' (npx/published daemon): node-pty was
  transitively bundled via @moonshot-ai/services (alwaysBundle), inlining its JS
  while its native binary can't be bundled and wasn't shipped. Mark node-pty
  external in tsdown (neverBundle) and declare it as a runtime dependency of
  @moonshot-ai/kimi-code so npm/npx installs it with its prebuilt pty.node.
- 'posix_spawnp failed' (local pnpm dev): node-pty's prebuilds/*/spawn-helper
  loses its +x bit through pnpm's store extraction. Add a root postinstall
  (scripts/fix-node-pty-perms.mjs) that restores the executable bit; verified it
  fixes a reproducible spawn failure.

Also harden defaultShell() to fall back on an empty (not just unset) $SHELL.

Note: the SEA standalone binary still needs node-pty's pty.node + spawn-helper
wired into scripts/native/native-deps.mjs (not addressed here; npx path covers
the reported case).
2026-06-14 01:10:22 +08:00

46 lines
1.8 KiB
JavaScript

#!/usr/bin/env node
/**
* Restore the executable bit on node-pty's `spawn-helper` prebuilt binaries.
*
* Why: on macOS/Linux node-pty launches the shell through a tiny `spawn-helper`
* executable shipped under `prebuilds/<platform-arch>/`. pnpm's content-
* addressable store does not preserve the +x mode on these non-bin prebuild
* assets, so after `pnpm install` the helper lands as 0644 and any PTY spawn
* fails with "posix_spawnp failed". npm/yarn (and the published tarball) keep
* the bit, so this is a pnpm-dev-only fixup.
*
* Idempotent and never fails the install: any error is logged and ignored.
*/
import { chmodSync, existsSync, readdirSync, statSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, join } from 'node:path';
function nodePtyRoot() {
const require = createRequire(import.meta.url);
// Resolve from packages/services (where node-pty is declared) so we find the
// workspace's hoisted copy regardless of where this script runs.
const entry = require.resolve('node-pty', {
paths: [join(process.cwd(), 'packages/services'), process.cwd()],
});
// .../node-pty/lib/index.js -> .../node-pty
return dirname(dirname(entry));
}
try {
const root = nodePtyRoot();
const prebuilds = join(root, 'prebuilds');
if (!existsSync(prebuilds)) process.exit(0);
let fixed = 0;
for (const arch of readdirSync(prebuilds)) {
const helper = join(prebuilds, arch, 'spawn-helper');
if (!existsSync(helper)) continue;
const mode = statSync(helper).mode;
if ((mode & 0o111) === 0o111) continue; // already executable
chmodSync(helper, 0o755);
fixed++;
}
if (fixed > 0) console.log(`[fix-node-pty-perms] made ${fixed} spawn-helper binary(ies) executable`);
} catch (err) {
console.warn('[fix-node-pty-perms] skipped:', err instanceof Error ? err.message : String(err));
}
process.exit(0);