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

  1. Append the actual filename to the path before reading.
  2. Call stat/list on the path first and branch: if type is 'directory', list it or read a specific child instead.
  3. Catch IsDirectoryError and fall back to listing the directory contents.
  4. 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

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


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/2455e7ad91c71858. Report an issue: GitHub.