evanw/esbuild · error

EvalSymlinks: too many links

Error message

EvalSymlinks: too many links

What it means

esbuild vendors Go's path/filepath.EvalSymlinks logic (internal/fs/filepath.go). While resolving a path it counts symlink hops and aborts after 255 (linksWalked > 255) to prevent infinite loops, mirroring the Go runtime's own guard. This protects esbuild from pathological filesystem trees when it canonicalizes source, output, watch, or plugin-supplied paths. The error propagates up as a failed path resolution during a build.

Source

Thrown at internal/fs/filepath.go:244

		// Resolve symlink.

		fi, err := os.Lstat(dest)
		if err != nil {
			return "", err
		}

		if fi.Mode()&os.ModeSymlink == 0 {
			if !fi.Mode().IsDir() && end < len(path) {
				return "", syscall.ENOTDIR
			}
			continue
		}

		// Found symlink.

		linksWalked++
		if linksWalked > 255 {
			return "", errors.New("EvalSymlinks: too many links")
		}

		link, err := os.Readlink(dest)
		if err != nil {
			return "", err
		}

		if isWindowsDot && !fp.isAbs(link) {
			// On Windows, if "." is a relative symlink,
			// just return ".".
			break
		}

		path = link + path[end:]

		v := fp.volumeNameLen(link)
		if v > 0 {
			// Symlink to drive name is an absolute path.

View on GitHub (pinned to 6ff1d8b0d8)

Solutions

  1. Find and remove the offending symlink cycle: run 'ls -la' on the path esbuild prints and trace the links, or use 'readlink -f' to see where it stalls.
  2. Point esbuild at the real (non-symlinked) directory — set entry points / outdir / absWorkingDir to the canonical location.
  3. Audit node_modules and build output dirs for accidental self-referential symlinks from generators.
  4. If on a network/overlay filesystem, verify it isn't synthesizing infinite link chains.

Example fix

# before: node_modules/foo symlinks back to project root, creating a loop
esbuild src/index.js --outfile=dist/index.js

# after: break the cycle and target the real path
readlink -f node_modules/foo   # inspect
rm node_modules/foo            # remove the bad link
esbuild src/index.js --outfile=dist/index.js
Defensive patterns

Strategy: validation

Validate before calling

const fs = require('fs');
function hasSymlinkLoop(p, max = 64) {
  const seen = new Set();
  let cur = p, n = 0;
  while (true) { try { cur = fs.realpathSync(cur); } catch { return false; } if (seen.has(cur)) return true; seen.add(cur); if (++n > max) return true; if (fs.lstatSync(cur).Mode & fs.constants.S_IFLNK === 0) break; }
  return false;
}

Try / catch

try { await esbuild.build({...}); } catch (e) { if (/too many links/.test(e.message)) { /* scan entry/outdir/watch paths for symlink cycles */ } throw e; }

Prevention

When it happens

Trigger: Any esbuild operation that canonicalizes a path (resolving an entry point, outdir, absWorkingDir, plugin WatchFiles/WatchDirs, or serve servedir) encounters a symlink chain longer than 255 hops. Most often a circular symlink (a -> b -> a) or a deeply nested symlink farm.

Common situations: A symlink loop created by a bad post-install script or monorepo hoisting (node_modules -> itself); a self-referential symlink in a generated directory; corrupted or intentionally cyclic filesystems; mounting artifacts where directories are symlinked in a ring.

Related errors


AI-assisted analysis of evanw/esbuild@6ff1d8b0d8 (2026-08-03). Data as JSON: /data/errors/538c5f3073fd45da.json. Report an issue: GitHub.