Skip to content

[Bug]: modern deploy fails with ENOENT/EACCES when nft traces system directories #8779

Description

@scplay

Version

System:
    OS: macOS 26.4.1
    CPU: (10) arm64 Apple M5
    Memory: 606.77 MB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Browsers:
    Chrome: 149.0.7827.201
    Chrome Canary: 152.0.7956.0
    Safari: 26.4
  npmPackages:
    @modern-js/app-tools: 2.68.18 => 2.68.18 
    @modern-js/plugin-tailwindcss: ^2.68.18 => 2.70.4 
    @modern-js/runtime: 2.68.18 => 2.68.18 
    @modern-js/server-runtime: 2.68.18 => 2.68.18 
    @modern-js/tsconfig: 2.68.18 => 2.68.18 
    @modern-js/types: 2.68.18 => 2.68.18

Details

When running modern deploy, the deploy plugin calls ndepe.nodeDepEmit() which invokes @vercel/nft's nodeFileTrace() with base: "/" and no ignore option.

In large SSR projects, the server bundle (.output/bundles/main.js) contains code patterns like:

  • fs.readdirSync(__dirname) (from various packages)
  • path.resolve(__dirname, variable) + readFile (from @modern-js/prod-server internals)
  • require(__dirname + '/../themes/...') (from colors package, a winston dependency)

These patterns trigger nft's emitAssetDirectory / emitWildcardRequire, which globs parent directories. Since base: "/", the glob reaches /etc/**/*.

On macOS:

Error: EACCES: permission denied, open '/etc/sudoers'
    at async CachedFileSystem._internalReadFile (@vercel/nft/out/fs.js:76:21)
    at async Job.emitDependency (@vercel/nft/out/node-file-trace.js:334:28)
    at async nodeFileTrace (@vercel/nft/out/node-file-trace.js:62:5)
    at async traceFiles (ndepe/dist/utils.js:201:17)
    at async nodeDepEmit (ndepe/dist/index.js:60:23)

On Linux CI (containers with stale symlinks):

Error: ENOENT: no such file or directory, stat '/etc/alternatives/which.sl1.gz'

The build output (.output/static) is generated successfully, but the subsequent ndepe dependency tracing step fails, preventing node_modules from being bundled into .output.

Call chain

modern deploy
  → @modern-js/app-tools deploy plugin (platforms/node.js)
    → ndepe.nodeDepEmit({ appDir, sourceDir: ".output" })
      → ndepe.traceFiles({ base: "/", /* no ignore */ })
        → @vercel/nft.nodeFileTrace(entries, { base: "/" })
          → static analysis finds __dirname patterns
          → emitAssetDirectory / emitWildcardRequire
          → Globbing /etc/**/*
          → EACCES or ENOENT on system files

Reproduce link

see Reproduce Steps

Reproduce Steps

mkdir nft-repro && cd nft-repro
cat > package.json << 'EOF'
{ "dependencies": { "@vercel/nft": "0.29.2" } }
EOF
cat > trigger.js << 'EOF'
var path = require('path');
var fs = require('fs');
function loadConfig(opts) { return fs.readFileSync(path.resolve(__dirname, opts.filename), 'utf-8'); }
function listFiles() { return fs.readdirSync(__dirname); }
module.exports = { loadConfig, listFiles };
EOF
pnpm install
node -e "
const {nodeFileTrace} = require('@vercel/nft');
const fs = require('fs');
nodeFileTrace([__dirname+'/trigger.js'], {base:'/', log:true,
  readFile: p=>fs.promises.readFile(p,'utf8').catch(e=>e.code==='EACCES'?'':null),
  stat: p=>fs.promises.stat(p).catch(()=>null),
  readlink: p=>fs.promises.readlink(p).catch(()=>null)
}).then(r=>{console.log('files:',r.fileList.size,[...r.fileList].filter(f=>f.startsWith('etc/')).length,'in /etc')});
"
  1. Create a Modern.js SSR project with server-side dependencies that produce the __dirname patterns in the server bundle (e.g. winston, @sentry/node, or any package using colors)
  2. Run MODERN_ENV=production modern deploy
  3. On macOS: fails with EACCES: permission denied, open '/etc/sudoers'
  4. On Linux CI with broken symlinks: fails with ENOENT

The issue is more likely to occur in large projects where rspack doesn't fully inline all dependencies into the server bundle.

ndepe should default-ignore system directories when calling nodeFileTrace({ base: "/" }):

const systemIgnores = ["etc/**", "private/etc/**", "dev/**", "sys/**", "proc/**"];
const res = await nodeFileTrace(entryFiles, {
  base,
  processCwd: sourceDir,
  cache,
  ...traceOptions,
  ignore: [...(traceOptions?.ignore || []), ...systemIgnores],
});

Alternatively, expose a deploy.traceOptions configuration in modern.config.ts so users can configure ignore patterns without patching.

I've also created a issue for nft vercel/nft#601

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions