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

  1. Set overwrite: true (or omit the option) if replacing the file is intended.
  2. Choose a unique target name (timestamp/uuid suffix) for create-once semantics.
  3. Treat FileExistsError as a success signal in idempotent flows — the artifact already exists, so read it instead.
  4. 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

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


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