mastra-ai/mastra · error · FileExistsError
EEXIST
EEXIST
Error message
File already exists: ${path} What it means
writeFile throws FileExistsError (code EEXIST) when options.overwrite === false and the target file already exists. The library writes with the atomic 'wx' flag so the existence check and write are a single filesystem operation, avoiding TOCTOU races — the error means the file genuinely existed at write time. Default behavior (overwrite unspecified/true) silently replaces the file.
Source
Thrown at packages/core/src/workspace/filesystem/local-filesystem.ts:463
// Compare via Date objects — Node's stats.mtime applies internal
// rounding that can diverge from Math.floor(stats.mtimeMs).
if (currentStat.mtime.getTime() !== options.expectedMtime.getTime()) {
throw new StaleFileError(inputPath, options.expectedMtime, currentStat.mtime);
}
} catch (error: unknown) {
if (error instanceof StaleFileError) throw error;
// File doesn't exist yet — no conflict possible, proceed with write
if (!isEnoentError(error)) throw error;
}
}
// Use 'wx' flag for atomic overwrite check (avoids TOCTOU race)
const writeFlag = options?.overwrite === false ? 'wx' : 'w';
try {
await fs.writeFile(absolutePath, this.toBuffer(content), { flag: writeFlag });
} catch (error: unknown) {
if (options?.overwrite === false && isEexistError(error)) {
throw new FileExistsError(inputPath);
}
throw error;
}
}
async appendFile(inputPath: string, content: FileContent): Promise<void> {
const contentSize = Buffer.isBuffer(content) ? content.length : content.length;
this.logger.debug('Appending to file', { path: inputPath, size: contentSize });
await this.ensureReady();
this.assertWritable('appendFile');
const absolutePath = this.resolvePath(inputPath);
await this.assertPathContained(absolutePath);
const dir = nodePath.dirname(absolutePath);
await fs.mkdir(dir, { recursive: true });
await fs.appendFile(absolutePath, this.toBuffer(content));
}
async deleteFile(inputPath: string, options?: RemoveOptions): Promise<void> {View on GitHub (pinned to 75dd419e61)
Solutions
- Set overwrite: true (or omit the option) if replacing the file is intended.
- Choose a unique target name (timestamp/uuid suffix) for create-once semantics.
- Treat FileExistsError as a success signal in idempotent flows — the artifact already exists, so read it instead.
- Catch FileExistsError and retry with a new filename or after checking the existing file's content.
Example fix
// before
await fs.writeFile('lock', 'me', { overwrite: false }); // FileExistsError on rerun
// after
try {
await fs.writeFile(`locks/${runId}.lock`, 'me', { overwrite: false });
} catch (e) {
if (e instanceof FileExistsError) return; // idempotent: already created
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
let exists = true;
try { await ws.stat(p); } catch { exists = false; }
if (exists && mustNotOverwrite) {
throw new Error(`${p} already exists; pick a unique name`);
} Type guard
import { FileExistsError } from '@mastra/core/workspace/errors';
function isFileExistsError(e: unknown): e is FileExistsError {
return e instanceof FileExistsError ||
(e instanceof Error && 'code' in e && (e as { code?: string }).code === 'EEXIST');
} Try / catch
try {
await ws.writeFile(p, data, { overwrite: false });
} catch (e) {
if (isFileExistsError(e)) {
return await ws.readFile(p, { encoding: 'utf8' }); // idempotent: reuse existing
}
throw e;
} Prevention
- Use overwrite: false only for create-once artifacts; it's the atomic 'wx' path.
- Generate unique names (runId/uuid/timestamp suffix) for anything written more than once.
- Treat EEXIST as success in idempotent/retry flows.
- Clean up or rotate old artifacts so create-once writes don't collide on reruns.
When it happens
Trigger: writeFile(path, data, { overwrite: false }) where path already exists; create-once semantics (e.g. writing an idempotency marker or new record file) colliding with an existing file; two concurrent create-if-absent writes — exactly one wins, the other gets EEXIST.
Common situations: Rerunning a job that already created its output file; agent retry after a timeout that actually succeeded the first time; naming collisions when generating files from user input (two requests named the same).
Related errors
- FileExistsError: ${path}
- ESTALE
- MASTRA_ENTRY_FILE_NOT_FOUND
- Failed to copy studio assets from "${studioSource}" to "${st
- Failed to copy studio assets from "${studioSource}" to "${st
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/7685b232969dab75.
Report an issue: GitHub.