denoland/deno · error

the source and destination are the same file

Error message

the source and destination are the same file

What it means

Before dispatching a copy, Deno lstats both paths and compares identity with is_identical(): inode equality on Unix; on Windows creation_time + last_write_time equality (a heuristic, since a stable file_index() is not exposed by std). On a match it refuses with io::ErrorKind::InvalidInput 'the source and destination are the same file' (ext/fs/std_fs.rs:918).

Source

Thrown at ext/fs/std_fs.rs:918

    {
      return cp_(
        source_meta,
        from,
        &to.join(from.file_name().ok_or_else(|| {
          io::Error::new(
            io::ErrorKind::InvalidInput,
            "the source path is not a valid file",
          )
        })?),
      );
    }
  }

  if let Ok(m) = fs::symlink_metadata(to)
    && is_identical(&source_meta, &m)
  {
    return Err(
      io::Error::new(
        io::ErrorKind::InvalidInput,
        "the source and destination are the same file",
      )
      .into(),
    );
  }

  cp_(source_meta, from, to)
}

#[cfg(not(windows))]
fn stat(path: &Path) -> FsResult<FsStat> {
  let metadata = fs::metadata(path)?;
  Ok(FsStat::from_std(metadata))
}

#[cfg(windows)]
fn stat(path: &Path) -> FsResult<FsStat> {

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Skip when the paths truly are the same file: compare Deno.realPathSync(from) === Deno.realPathSync(to)
  2. On Windows false positives: remove the stale destination first (Deno.removeSync(to)) so the metadata comparison no longer matches
  3. Compare size/content hash yourself when timestamps are suspect
  4. If distinct files keep being flagged, report it — the Windows check is time-based by design

Example fix

// before
await Deno.copy(src, dst); // false positive: same timestamps on Windows

// after
if (Deno.build.os === 'windows') {
  try { Deno.removeSync(dst); } catch { /* not present */ }
}
await Deno.copy(src, dst);
Defensive patterns

Strategy: validation

Validate before calling

function sameRealPath(from: string, to: string): boolean {
  try {
    return Deno.realPathSync(from) === Deno.realPathSync(to);
  } catch {
    return false;
  }
}

if (!sameRealPath(src, dst)) {
  if (Deno.build.os === 'windows') {
    try { Deno.removeSync(dst); } catch { /* absent */ } // defeat timestamp heuristic
  }
  await Deno.copy(src, dst);
}

Try / catch

try {
  await Deno.copy(src, dst);
} catch (e) {
  if (e instanceof Error && /the same file/.test(e.message)) {
    // distinguish real duplicates (same realPath) from Windows timestamp false positives
    if (Deno.realPathSync(src) === Deno.realPathSync(dst)) return;
    Deno.removeSync(dst);
    await Deno.copy(src, dst);
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Destination is a hard link or symlink to the source (same inode on Unix); equivalent path spellings; on Windows, the timestamp heuristic can misfire on two genuinely different files that share creation and modification times — e.g. files extracted from the same archive or emitted by one code-generator run.

Common situations: Copy scripts where TO is inside FROM through links; CI artifacts with mass-identical timestamps; false positives reported on Windows after zip extraction preserves mtimes.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/174475bb2e4d2706. Report an issue: GitHub.