mcpb pack stops with a raw ENOENT when the directory holds a broken symlink. The message names a path that the user can see on disk, so it reads as a wrong error. Nothing says the entry is a symlink, and nothing says which entry stopped the pack.
This is next to #292 in the same walk, but it is a different case. #292 is a symlink cycle and gives ELOOP. This one is a link whose target does not exist and gives ENOENT. PR #293 fixes the cycle only, so this stays after that PR lands.
Steps to reproduce
mkdir -p demo && cd demo
cat > manifest.json <<'M'
{ "manifest_version": "0.1", "name": "demo", "version": "1.0.0",
"description": "demo", "author": { "name": "demo" },
"server": { "type": "node", "entry_point": "index.js",
"mcp_config": { "command": "node", "args": ["index.js"] } } }
M
touch index.js
ln -s ./nowhere dangling
mcpb pack . ../demo.mcpb
Expected
The pack skips the broken link and writes the bundle, or it stops with a message that names the link and its missing target.
Actual
Validating manifest...
Manifest schema validation passes!
ERROR: Archive error: ENOENT: no such file or directory, stat '/.../demo/dangling'
Exit code is 1 and no .mcpb file is written.
Cause
getAllFiles calls statSync(filePath) at src/node/files.ts:97. statSync follows the link and throws when the target is gone. getAllFilesWithCount has the same call at src/node/files.ts:140. Neither call is guarded, so one broken link stops the whole pack.
lstatSync reports the link itself and does not throw here. A check on stat.isSymbolicLink() before the follow would separate the two cases.
Why this happens in real trees
A broken link is normal in a working directory. node_modules/.bin keeps links to package binaries, and a prune or a partial install leaves some of them pointing at nothing. A link to a file that a later commit deleted does the same.
Workaround
Name the link in .mcpbignore. The pack then succeeds. That needs the author to already know which link is broken.
Version information
mcpb --version: 2.1.2
- Built from
main at 70fe3b3
- Node v24.18.0, macOS 26.6.2
A control in the same run packs correctly. A symlink that points at a real sibling directory gives 4 files and a valid bundle. Only the broken link fails.
I am happy to send a patch for this on top of #293, so the two changes do not conflict.
mcpb packstops with a raw ENOENT when the directory holds a broken symlink. The message names a path that the user can see on disk, so it reads as a wrong error. Nothing says the entry is a symlink, and nothing says which entry stopped the pack.This is next to #292 in the same walk, but it is a different case. #292 is a symlink cycle and gives
ELOOP. This one is a link whose target does not exist and givesENOENT. PR #293 fixes the cycle only, so this stays after that PR lands.Steps to reproduce
Expected
The pack skips the broken link and writes the bundle, or it stops with a message that names the link and its missing target.
Actual
Exit code is 1 and no
.mcpbfile is written.Cause
getAllFilescallsstatSync(filePath)atsrc/node/files.ts:97.statSyncfollows the link and throws when the target is gone.getAllFilesWithCounthas the same call atsrc/node/files.ts:140. Neither call is guarded, so one broken link stops the whole pack.lstatSyncreports the link itself and does not throw here. A check onstat.isSymbolicLink()before the follow would separate the two cases.Why this happens in real trees
A broken link is normal in a working directory.
node_modules/.binkeeps links to package binaries, and a prune or a partial install leaves some of them pointing at nothing. A link to a file that a later commit deleted does the same.Workaround
Name the link in
.mcpbignore. The pack then succeeds. That needs the author to already know which link is broken.Version information
mcpb --version: 2.1.2mainat 70fe3b3A control in the same run packs correctly. A symlink that points at a real sibling directory gives 4 files and a valid bundle. Only the broken link fails.
I am happy to send a patch for this on top of #293, so the two changes do not conflict.