Skip to content

Handle broken symlinks gracefully during file tracing #601

Description

@scplay

Bug Description

When @vercel/nft traces dependencies in an environment containing broken symlinks
(symlinks whose target does not exist), the tracing process throws an unhandled
ENOENT error and aborts entirely.

Steps to Reproduce

  1. Create a broken symlink in the system (e.g., /etc/alternatives/which.sl1.gz
    pointing to a non-existent target)
  2. Run nodeFileTrace() on an entry file whose dependency chain resolves through
    that directory (e.g., via a binary that references /usr/bin/which
    /etc/alternatives/which)
  3. The trace fails with: Error: ENOENT: no such file or directory, stat '/etc/alternatives/which.sl1.gz'

Expected Behavior

Broken symlinks should be skipped gracefully (similar to how _internalReadlink
already handles ENOENT by returning null). The trace should continue and
simply not include the broken symlink target in the output fileList.

Actual Behavior

The entire trace aborts with an unhandled ENOENT error, preventing any output
from being generated.

Environment

  • @vercel/nft version: 0.29.2
  • Node.js: 20.x
  • OS: Linux (CI container based on CentOS/Alpine with stale symlinks in
    /etc/alternatives)

Analysis

In src/fs.ts, _internalStat correctly returns null for ENOENT. However,
the realpath() method in src/node-file-trace.ts recursively resolves paths
by calling readlinkresolverealpath again. During this recursion,
when the resolved path traverses a directory that contains broken symlinks,
subsequent operations on adjacent files (like glob-based asset emission or
shared library detection) may attempt to stat those broken symlinks through
a code path that does not handle the null/ENOENT return gracefully.

Suggested Fix

Ensure that any code path that calls stat() or processes directory entries
during tracing handles broken symlinks (stat returning null) by skipping
the entry rather than propagating the error.

Metadata

Metadata

Assignees

No one assigned

    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