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
- Check `memoryFS.symlinks.has(filePath)` before calling readlinkSync when using MemoryFS directly.
- Wrap readlinkSync in try/catch and treat EINVAL as 'not a symlink' by returning the path unchanged.
- 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 traversing paths, only call readlink on paths known to be symlinks.
- Wrap readlink in a helper that returns null for non-symlinks instead of throwing.
- For OverlayFS, use its built-in _isSymlink() method which safely checks both layers.
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
- Unknown entry: ${entry}
- ELOOP
- NotFound
- ${path.relative(process.cwd(), source)} is not a file.
- Could not find parcel config at ${path.relative(options.proj
AI-assisted analysis of parcel-bundler/parcel@59484858a1 (2026-08-13).
Data as JSON: /api/errors/8c95f6f3330e01f4.
Report an issue: GitHub.