← Debugging Guides

阅读中文版本

Node EEXIST — File Already Exists When Writing

Fix Node EEXIST errors from mkdir, copyFile, and write-file-atomic by choosing the right flag and treating concurrent creation as normal.

beginnerdebuggingnodejsfilesystemidempotency

Symptoms

  • Node throws "Error: EEXIST: file already exists, mkdir ..."
  • A build or deploy script fails when run a second time
  • Multiple workers start at once and all try to create the same directory

Possible causes

  • mkdir is called without recursive:true, so an existing directory is an error
  • copyFile uses the default COPYFILE_EXCL flag semantics, refusing to overwrite
  • Concurrent processes race to create the same path

Diagnosis steps

Identify the failing operation and flag

node -e "require('fs').mkdir('/tmp/x/child', {}, e=>console.log(e && e.code))"

Interpretation

EEXIST with mkdir means the directory exists; the fix is recursive:true, not deleting the path.

Check whether the path is a file or a directory

node -e "const s=require('fs').statSync('/tmp/x'); console.log(s.isDirectory())"

Interpretation

If a file occupies the directory path, creation fails for a different reason and must be handled explicitly.

Root cause

Node fs operations are strict by default: mkdir without recursive and copyFile without flags refuse to act on an existing target and raise EEXIST. Concurrent creators make the error intermittent, which looks like a race but is really a missing idempotency flag.

Fix

Use fs.mkdir(path, { recursive: true }) and fs.cp/copyFile with the intended overwrite mode, or guard with a try/catch that treats EEXIST as success. Avoid delete-then-create, which reintroduces a race window.

Risk note

Treating every EEXIST as success hides the case where a regular file occupies the directory path and writes will silently fail.

Verify the fix

Run the script twice and from two processes at once; both runs should exit 0 and the directory should exist exactly once. Assert the path is a directory afterwards.

Symptoms

The error “Node EEXIST — File Already Exists When Writing” points at a specific failure mode. This guide documents the symptoms you can observe, the commands that isolate the cause, the root cause, the fix, and the checks that prove the fix works.

Observed symptoms include:

  • Node throws “Error: EEXIST: file already exists, mkdir …”
  • A build or deploy script fails when run a second time
  • Multiple workers start at once and all try to create the same directory

Reproduce and Diagnose

Reproduce the failure in isolation before changing any code. Work through the checks below in order; each one narrows the search space.

Step 1: Identify the failing operation and flag

node -e "require('fs').mkdir('/tmp/x/child', {}, e=>console.log(e && e.code))"

EEXIST with mkdir means the directory exists; the fix is recursive:true, not deleting the path.

Step 2: Check whether the path is a file or a directory

node -e "const s=require('fs').statSync('/tmp/x'); console.log(s.isDirectory())"

If a file occupies the directory path, creation fails for a different reason and must be handled explicitly.

Likely contributors, in order of frequency:

  • mkdir is called without recursive:true, so an existing directory is an error
  • copyFile uses the default COPYFILE_EXCL flag semantics, refusing to overwrite
  • Concurrent processes race to create the same path

Root Cause

Node fs operations are strict by default: mkdir without recursive and copyFile without flags refuse to act on an existing target and raise EEXIST. Concurrent creators make the error intermittent, which looks like a race but is really a missing idempotency flag.

The Fix

Use fs.mkdir(path, { recursive: true }) and fs.cp/copyFile with the intended overwrite mode, or guard with a try/catch that treats EEXIST as success. Avoid delete-then-create, which reintroduces a race window.

Watch out: Treating every EEXIST as success hides the case where a regular file occupies the directory path and writes will silently fail.

import { mkdir, cp } from 'node:fs/promises';

await mkdir('/var/app/cache', { recursive: true }); // idempotent
try {
  await cp('src', '/var/app/cache/src', { recursive: true, force: true });
} catch (err) {
  if (err.code !== 'EEXIST') throw err; // EEXIST here is benign
}

Verify and Prevent

Run the script twice and from two processes at once; both runs should exit 0 and the directory should exist exactly once. Assert the path is a directory afterwards.

Once verified, add a regression test that reproduces the original symptom so the failure cannot silently return.

This class of failure is usually environmental rather than pure logic, so capture the exact command, the environment variables, and the working directory in your bug report. Reproduce it in the smallest possible script, then widen the scope only once the minimal case is understood. A short written checklist for this failure mode saves hours the next time it appears.