denoland/deno · error · Error

A file exists at the destination: ${destStr}

Error message

A file exists at the destination: ${destStr}

What it means

fs.copyFileSync with fs.constants.COPYFILE_EXCL performs a no-clobber copy: it lstats the destination and, if anything exists there (file, directory, symlink), throws 'A file exists at the destination: ...' - the polyfill's equivalent of Node's EEXIST. Without the flag the destination is silently overwritten.

Source

Thrown at ext/node/polyfills/_fs/_fs_copy.ts:95

export const copyFilePromise = promisify(copyFile) as (
  src: string | Buffer | URL,
  dest: string | Buffer | URL,
  mode?: number,
) => Promise<void>;

export function copyFileSync(
  src: string | Buffer | URL,
  dest: string | Buffer | URL,
  mode?: number,
) {
  const srcStr = getValidatedPathToString(src, "src");
  const destStr = getValidatedPathToString(dest, "dest");
  const modeNum = getValidMode(mode, "copyFile");

  if ((modeNum & fs.COPYFILE_EXCL) === fs.COPYFILE_EXCL) {
    try {
      Deno.lstatSync(destStr);
      throw new Error(`A file exists at the destination: ${destStr}`);
    } catch (e) {
      if (ObjectPrototypeIsPrototypeOf(Deno.errors.NotFound.prototype, e)) {
        Deno.copyFileSync(srcStr, destStr);
      } else {
        throw e;
      }
    }
  } else {
    Deno.copyFileSync(srcStr, destStr);
  }
}

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. If overwriting is acceptable, drop the COPYFILE_EXCL flag
  2. Treat the error as 'already done': catch it and skip/continue
  3. Pre-check with fs.existsSync(dst) or fs.lstatSync when you need custom handling per case

Example fix

// before
fs.copyFileSync(src, dst, fs.constants.COPYFILE_EXCL); // throws if dst exists

// after
try {
  fs.copyFileSync(src, dst, fs.constants.COPYFILE_EXCL);
} catch (e) {
  if (
    e instanceof Error &&
    e.message.startsWith("A file exists at the destination")
  ) {
    // previously copied - treat as success
  } else {
    throw e;
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

import * as fs from "node:fs";

function copyNoClobber(src: string, dst: string): "copied" | "existed" {
  if (fs.existsSync(dst)) return "existed"; // lstat-based pre-check
  try {
    fs.copyFileSync(src, dst, fs.constants.COPYFILE_EXCL);
    return "copied";
  } catch {
    return "existed"; // lost the race - treat as already present
  }
}

Try / catch

try {
  fs.copyFileSync(src, dst, fs.constants.COPYFILE_EXCL);
} catch (e) {
  if (e instanceof Error && e.message.startsWith("A file exists at the destination")) {
    // Deno polyfill: plain Error (Node sets code EEXIST) - treat as idempotent success
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: fs.copyFileSync(src, dst, fs.constants.COPYFILE_EXCL) where dst already exists - commonly from a previous run of the same script that partially completed.

Common situations: Idempotent install/bootstrap scripts re-run after partial completion; backup routines protecting existing archives; concurrent processes racing to create the same destination file.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/e24952ac0ff038db. Report an issue: GitHub.