mastra-ai/mastra · error · IsDirectoryError
EISDIR
EISDIR
Error message
Path is a directory: ${path} What it means
readFile throws IsDirectoryError (code EISDIR) when the requested path resolves to a directory rather than a regular file. The stat happens after containment checks and before the actual fs.readFile, so directories are rejected with a clear typed error instead of the raw Node EISDIR. Use list/readdir-style APIs to inspect directories; readFile is for files only.
Source
Thrown at packages/core/src/workspace/filesystem/local-filesystem.ts:394
const isWithinRoot = rootReals.some(
rootReal => targetReal === rootReal || targetReal.startsWith(rootReal + nodePath.sep),
);
if (!isWithinRoot) {
throw new PermissionError(absolutePath, 'access');
}
}
async readFile(inputPath: string, options?: ReadOptions): Promise<string | Buffer> {
this.logger.debug('Reading file', { path: inputPath, encoding: options?.encoding });
await this.ensureReady();
const absolutePath = this.resolvePath(inputPath);
await this.assertPathContained(absolutePath);
try {
const stats = await fs.stat(absolutePath);
if (stats.isDirectory()) {
throw new IsDirectoryError(inputPath);
}
if (options?.encoding) {
return await fs.readFile(absolutePath, { encoding: options.encoding });
}
return await fs.readFile(absolutePath);
} catch (error: unknown) {
if (error instanceof IsDirectoryError) throw error;
if (isEnoentError(error)) {
throw new FileNotFoundError(inputPath);
}
throw error;
}
}
async writeFile(inputPath: string, content: FileContent, options?: WriteOptions): Promise<void> {
const contentSize = Buffer.isBuffer(content) ? content.length : content.length;
this.logger.debug('Writing file', { path: inputPath, size: contentSize, recursive: options?.recursive });View on GitHub (pinned to 75dd419e61)
Solutions
- Append the actual filename to the path before reading.
- Call stat/list on the path first and branch: if type is 'directory', list it or read a specific child instead.
- Catch IsDirectoryError and fall back to listing the directory contents.
- Verify the path exists as a file with fs.stat before calling readFile.
Example fix
// before
const txt = await fs.readFile('docs'); // IsDirectoryError
// after
const entries = await fs.list('docs');
const txt = await fs.readFile('docs/index.md'); Defensive patterns
Strategy: validation
Validate before calling
const s = await ws.stat(p);
if (s.type === 'directory') {
// list it or read a specific child instead of readFile
}
await ws.readFile(p); Type guard
import { IsDirectoryError } from '@mastra/core/workspace/errors';
function isDirectoryError(e: unknown): e is IsDirectoryError {
return e instanceof IsDirectoryError ||
(e instanceof Error && 'code' in e && (e as { code?: string }).code === 'EISDIR');
} Try / catch
try {
return await ws.readFile(p, { encoding: 'utf8' });
} catch (e) {
if (isDirectoryError(e)) {
return await ws.list(e.path).then(entries => entries.map(x => x.name));
}
throw e;
} Prevention
- Branch on FileStat.type from stat/list results before reading.
- Never build read paths from directory listings without taking entry.name.
- Require an explicit filename in user/model supplied paths.
- When a path may be either, stat first — it's one call and avoids the error entirely.
When it happens
Trigger: workspace.filesystem.readFile('src') where src is a directory; passing a directory path to the 'content' helper; a path the caller assumed was a file (e.g. from a fuzzy name match or missing extension) is actually a directory.
Common situations: Agent builds a path by forgetting the filename ('notes/' vs 'notes/todo.md'); user data contains a directory where a file was expected (e.g. a directory named 'README.md'); glob/list results truncated so the file portion of the path was dropped.
Related errors
- MASTRA_ENTRY_FILE_NOT_FOUND
- Failed to copy studio assets from "${studioSource}" to "${st
- Failed to copy studio assets from "${studioSource}" to "${st
- Failed to read studio routes manifest at "${manifestPath}":
- pull-failed
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/2455e7ad91c71858.
Report an issue: GitHub.