mastra-ai/mastra · error · NotDirectoryError
ENOTDIR
ENOTDIR
Error message
Path is not a directory: ${path} What it means
writeFile throws NotDirectoryError (code ENOTDIR) when options.recursive === false and the parent directory of the target path exists but is a regular file, not a directory. In non-recursive mode the library requires the immediate parent to be an existing directory; anything else is rejected before writing. (If the parent is missing entirely you get DirectoryNotFoundError instead.)
Source
Thrown at packages/core/src/workspace/filesystem/local-filesystem.ts:425
}
}
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 });
await this.ensureReady();
this.assertWritable('writeFile');
const absolutePath = this.resolvePath(inputPath);
await this.assertPathContained(absolutePath);
// When recursive is explicitly false, verify parent directory exists
if (options?.recursive === false) {
const dir = nodePath.dirname(absolutePath);
const parentPath = nodePath.dirname(inputPath);
try {
const stat = await fs.stat(dir);
if (!stat.isDirectory()) {
throw new NotDirectoryError(parentPath);
}
} catch (error: unknown) {
if (error instanceof NotDirectoryError) throw error;
if (isEnoentError(error)) {
throw new DirectoryNotFoundError(parentPath);
}
throw error;
}
}
if (options?.recursive !== false) {
const dir = nodePath.dirname(absolutePath);
await fs.mkdir(dir, { recursive: true });
}
// Optimistic concurrency: reject if file was modified since caller last read it
if (options?.expectedMtime) {
try {View on GitHub (pinned to 75dd419e61)
Solutions
- Remove recursive: false (or set recursive: true) so missing parent directories are created automatically — this is the default.
- Rename/delete the file that is blocking the directory name, then write again.
- Verify with stat that each path segment used as a directory is actually a directory.
- Choose a target path whose parent is a real directory.
Example fix
// before
await fs.writeFile('data/child.txt', 'x', { recursive: false }); // ENOTDIR if 'data' is a file
// after
await fs.writeFile('data/child.txt', 'x'); // recursive defaults to true; or free the 'data' name first Defensive patterns
Strategy: validation
Validate before calling
const parent = nodePath.posix.dirname(p);
const s = await ws.stat(parent);
if (s.type !== 'directory') {
throw new Error(`${parent} is not a directory; fix the path or enable recursive writes`);
} Type guard
import { NotDirectoryError } from '@mastra/core/workspace/errors';
function isNotDirectoryError(e: unknown): e is NotDirectoryError {
return e instanceof NotDirectoryError ||
(e instanceof Error && 'code' in e && (e as { code?: string }).code === 'ENOTDIR');
} Try / catch
try {
await ws.writeFile(p, data, { recursive: false });
} catch (e) {
if (isNotDirectoryError(e)) {
await ws.writeFile(p, data); // fall back to recursive (auto-mkdir)
return;
}
throw e;
} Prevention
- Only use recursive: false when you truly need strict behavior; the default creates parents safely.
- Keep file and directory names from colliding in your layout conventions.
- Stat path segments that double as names before using them as directories.
- Reserve recursive: false for guarded internals, not for paths derived from user input.
When it happens
Trigger: writeFile('a/b.txt', data, { recursive: false }) where 'a' is an existing file; path segments colliding, e.g. a file named 'data' already exists and you write 'data/child.txt'; passing recursive: false explicitly when the caller assumed directories are auto-created.
Common situations: A previously written file occupies the name now used as a directory; workspace was seeded with a flat file layout that conflicts with nested paths; copy-pasted options keeping recursive: false from a strict-mode call.
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/c99b5f9b065ce5c8.
Report an issue: GitHub.