parcel-bundler/parcel · error · FSError

EINVAL

EINVAL

Error message

is not a symlink

What it means

Thrown by MemoryFS.readlinkSync() when the given filePath has no entry in the internal `symlinks` Map. MemoryFS stores symlinks separately from files and directories; readlink only consults the symlinks map. The error code EINVAL matches Node.js fs.readlink behavior for non-symlink paths.

Source

Thrown at packages/core/fs/src/MemoryFS.js:550

  }

  createWriteStream(filePath: FilePath, options: ?FileOptions): WriteStream {
    return new WriteStream(this, filePath, options);
  }

  realpathSync(filePath: FilePath): FilePath {
    return this._normalizePath(filePath);
  }

  // eslint-disable-next-line require-await
  async realpath(filePath: FilePath): Promise<FilePath> {
    return this.realpathSync(filePath);
  }

  readlinkSync(filePath: FilePath): FilePath {
    let symlink = this.symlinks.get(filePath);
    if (!symlink) {
      throw new FSError('EINVAL', filePath, 'is not a symlink');
    }
    return symlink;
  }

  // eslint-disable-next-line require-await
  async readlink(filePath: FilePath): Promise<FilePath> {
    return this.readlinkSync(filePath);
  }

  async symlink(target: FilePath, path: FilePath) {
    target = this._normalizePath(target);
    path = this._normalizePath(path);
    this.symlinks.set(path, target);
    await this._sendWorkerEvent({
      type: 'symlink',
      path,
      target,
    });

View on GitHub (pinned to 59484858a1)

Solutions

  1. Check `memoryFS.symlinks.has(filePath)` before calling readlinkSync when using MemoryFS directly.
  2. Wrap readlinkSync in try/catch and treat EINVAL as 'not a symlink' by returning the path unchanged.
  3. Use the OverlayFS wrapper which has `_isSymlink()` that safely checks both layers.

Example fix

// before
let target = fs.readlinkSync(filePath);

// after
let target;
try {
  target = fs.readlinkSync(filePath);
} catch (e) {
  if (e.code !== 'EINVAL') throw e;
  target = filePath; // not a symlink, return as-is
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Check symlinks Map before readlinkSync (MemoryFS-specific)
function safeReadlinkSync(fs, filePath) {
  filePath = fs._normalizePath ? fs._normalizePath(filePath) : filePath;
  if (fs.symlinks?.has(filePath)) {
    return fs.readlinkSync(filePath);
  }
  return null; // not a symlink
}

Type guard

// Guard: check if the filesystem exposes a symlinks Map
function hasSymlinkMap(fs) {
  return fs instanceof Object && fs.symlinks instanceof Map;
}

Try / catch

try {
  let target = fs.readlinkSync(filePath);
} catch (e) {
  if (e.code === 'EINVAL') {
    // Not a symlink — proceed with regular file logic
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling `memoryFS.readlinkSync('/some/path')` where the path was never registered via `memoryFS.symlink(target, path)`, or where the symlink was deleted. Also triggered when code assumes a path is a symlink but it's actually a regular file or directory.

Common situations: A Parcel resolver traverses a path and calls readlink on every segment to detect symlinks, but some segments are regular files. Plugin code copies logic from NodeFS without checking `lstat().isSymbolicLink()` first. MemoryFS doesn't support lstatSync, so callers can't pre-check symlink type easily.

Related errors


AI-assisted analysis of parcel-bundler/parcel@59484858a1 (2026-08-13). Data as JSON: /api/errors/8c95f6f3330e01f4. Report an issue: GitHub.