Troubleshooting
Check this first
Most pkg issues come from one of three things:
- Assets not detected — files you expect in the binary aren't there. Build with
--debugand run withDEBUG_PKG=1to list the snapshot tree. See Debug virtual FS. NODE_OPTIONSleaking in from an IDE or shell. Some options conflict with pkg — see NODE_OPTIONS conflicts.- Stale cached base binary — blow away
~/.pkg-cacheand retry.
Error: ERR_REQUIRE_ESM
Full message: Error [ERR_REQUIRE_ESM]: require() of ES Module.
Tracked by #16. In most cases, adding --options experimental-require-module to the pkg command line solves it:
pkg app.js --options experimental-require-moduleNode.js >= 22.12.0
This option is not needed from Node.js v22.12.0 onwards — experimental-require-module is the default. Drop the flag if you've upgraded your pkg target.
Error: Cannot find module XXX (when using child_process)
When you use child_process methods inside a packaged binary, pkg by default invokes the child using the packaged Node.js runtime, not the system node. If you're trying to spawn some external binary that happens to pass through node, or if the child accidentally re-enters the packaged entry file, you'll hit a Cannot find module error.
Fix: explicitly unset PKG_EXECPATH in the child's environment:
const { spawn } = require('child_process');
const child = spawn(process.execPath, [process.argv[1]], {
env: {
...process.env,
PKG_EXECPATH: '', // tell child: you are NOT inside a pkg binary
},
});This tells the child process to behave like a plain Node.js invocation instead of a pkg-wrapped one. More context in PR #90.
Error: Cannot execute binary from snapshot
Binaries inside the /snapshot/ virtual filesystem can't be exec()'d directly — the OS needs a real file on a real filesystem to spawn it. Extract it first:
const cp = require('child_process');
const fs = require('fs');
const { pipeline } = require('stream/promises');
let ffmpeg = require('@ffmpeg-installer/ffmpeg').path;
async function loadPlugin() {
if (process.pkg) {
// copy ffmpeg to the current directory
const file = fs.createWriteStream('ffmpeg');
await pipeline(fs.createReadStream(ffmpeg), file);
fs.chmodSync('ffmpeg', 0o755);
console.log('ffmpeg copied to the current directory');
ffmpeg = './ffmpeg';
}
cp.execSync(ffmpeg);
}
loadPlugin();For .node native addons, pkg does this extraction automatically — see Native addons. For arbitrary binaries (ffmpeg, ripgrep, protoc, …) you have to do it yourself.
Error: ENOENT: no such file or directory, uv_chdir
Usually means the directory the app was launched from (process.cwd()) was deleted while the app was still running. Check your code for anything that removes process.cwd() — temp directories in /tmp are common culprits. Not a pkg bug; reproduces under plain node too.
Error: ERR_INSPECTOR_NOT_AVAILABLE
Caused by NODE_OPTIONS forcing node into debug mode. Debugging options are intentionally disallowed inside packaged binaries — pkg executables are usually used in production environments where exposing an inspector port is a security risk.
Workarounds:
- Unset
NODE_OPTIONSbefore launching:shNODE_OPTIONS='' ./app - Or build a debuggable Node.js yourself and use it as a custom base binary — see the issue #93 workaround and Custom Node.js binary.
Error: require(...).internalModuleStat is not a function
Same root cause as the inspector error — NODE_OPTIONS (typically set automatically by IDEs like VS Code) is injecting bootstrap options that conflict with pkg's prelude.
On Unix (Linux / macOS):
printenv | grep NODEOn Windows PowerShell:
Get-ChildItem Env: | Where-Object Name -match '^NODE'Fix: clear NODE_OPTIONS and any other NODE_* variables in the terminal where you run the binary:
unset NODE_OPTIONS NODE_DEBUG NODE_EXTRA_CA_CERTS NODE_NO_WARNINGS
./appIf your shell or IDE re-adds them automatically, add an exception rule — e.g. in VS Code add "terminal.integrated.env.linux": { "NODE_OPTIONS": null } to disable the auto-injection just for this project.
Error: RangeError: pkg: Intl.Segmenter is unavailable in this executable
Standard-mode binaries embed a Node.js built with small-icu, which ships no ICU break-iterator data. new Intl.Segmenter() constructs fine, but segment() has no data to work with.
Upstream Node.js crashes the process outright here — an uncatchable SIGSEGV with no stack trace (nodejs/node#51752). pkg replaces segment() with this RangeError so the failure points at its caller instead.
You will usually hit this through a dependency rather than directly: string-width v7+ calls Intl.Segmenter to measure text, and it is a transitive dependency of ora, boxen, inquirer and cli-table3. Spinner frames are the classic trigger.
Pick whichever fits:
- Build with
--sea. SEA mode uses the official Node.js binaries, which are full-icu. Nothing else to do. - Ship ICU data alongside the executable. Download the
icudt<N>l.datmatchingprocess.versions.icufrom the ICU releases and pointNODE_ICU_DATAat its directory. pkg detects the data and steps aside. This costs you the single-file property, and pkg cannot do it for you — ICU initializes before any user code runs, so the file cannot come from the virtual filesystem. - Avoid the API. Pin
string-widthto v6, or shimIntl.Segmenterbefore importing anything that pulls it in.
AI-assisted debugging with Claude Code
If you use Claude Code, you can install the /pkg-debug skill to get interactive AI-assisted troubleshooting for any packaging issue.
Install the skill
Download the skill file into your project's .claude/skills/ directory:
mkdir -p .claude/skills/pkg-debug
curl -fsSL https://raw.githubusercontent.com/yao-pkg/pkg/main/.claude/skills/pkg-debug/SKILL.md \
-o .claude/skills/pkg-debug/SKILL.mdAlready cloned the repo?
If you're working inside a clone of yao-pkg/pkg, the skill is already available — no extra setup needed.
Use it
Start a new Claude Code session (skills are loaded at session start), then invoke it with a description of your issue:
/pkg-debug my binary crashes with "Cannot find module X"
/pkg-debug binary is 500 MB, how do I reduce size?
/pkg-debug cross-compile from linux to macos not working
/pkg-debug native addon fails to load after packagingThe skill covers build failures, runtime crashes, missing assets, binary bloat, cross-compile regressions, native addons, SEA issues, and the patches / dictionary systems — all linked back to these docs.
Still stuck?
- Search existing issues: github.com/yao-pkg/pkg/issues
- Open a new one with the output of
pkg --debugandDEBUG_PKG=1 ./your-binary - Check the recipes for an example that matches your setup
