denoland/deno · error · FsError

failed to copy '{}' to '{}': {:?}

Error message

failed to copy '{}' to '{}': {:?}

What it means

When Deno.copy()/copySync() copies a directory recursively, each entry is lstat'ed with fs::symlink_metadata before dispatch. If that stat fails, the original error is re-thrown with the same ErrorKind but a formatted message "failed to copy '<from>' to '<to>': <original error>" so the failing entry is identifiable (ext/fs/std_fs.rs:780).

Source

Thrown at ext/fs/std_fs.rs:780

      // continue copying all entries instead of aborting.
      if let Err(err) = builder.create(to)
        && err.kind() != ErrorKind::AlreadyExists
      {
        return Err(FsError::Io(err));
      }

      let mut entries: Vec<_> = fs::read_dir(from)?
        .map(|res| res.map(|e| e.file_name()))
        .collect::<Result<_, _>>()?;

      entries.shrink_to_fit();
      entries
        .into_par_iter()
        .map(|file_name| {
          let from_path = from.join(&file_name);
          let to_path = to.join(&file_name);
          let meta = fs::symlink_metadata(&from_path).map_err(|err| {
            io::Error::new(
              err.kind(),
              format!(
                "failed to copy '{}' to '{}': {:?}",
                from_path.display(),
                to_path.display(),
                err,
              ),
            )
          })?;
          cp_(meta, &from_path, &to_path).map_err(|err| {
            io::Error::new(
              err.kind(),
              format!(
                "failed to copy '{}' to '{}': {:?}",
                from_path.display(),
                to_path.display(),
                err,
              ),

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Verify read access before copying: Deno.permissions.querySync({ name: 'read', path: fromDir })
  2. Copy from a stabilized snapshot (stop writers, or tar first) when the tree is mutating
  3. Copy entries individually and skip entries that vanish mid-copy (NotFound race)
  4. Retry the copy once — transient races usually clear

Example fix

// before
await Deno.copy(srcDir, dstDir); // one vanished entry fails the whole copy

// after
await Deno.mkdir(dstDir, { recursive: true });
for (const entry of Deno.readDirSync(srcDir)) {
  try {
    await Deno.copy(`${srcDir}/${entry.name}`, `${dstDir}/${entry.name}`);
  } catch (e) {
    if (e instanceof Deno.errors.NotFound) continue; // vanished mid-copy
    throw e;
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

async function canReadTree(path: string): Promise<boolean> {
  const q = await Deno.permissions.query({ name: 'read', path });
  if (q.state !== 'granted') return false;
  try {
    for (const _ of Deno.readDirSync(path)) break; // probe read+search access
    return true;
  } catch {
    return false;
  }
}

Try / catch

for (const entry of Deno.readDirSync(srcDir)) {
  try {
    await Deno.copy(`${srcDir}/${entry.name}`, `${dstDir}/${entry.name}`);
  } catch (e) {
    if (e instanceof Deno.errors.NotFound) continue; // entry vanished mid-copy
    if (e instanceof Deno.errors.PermissionDenied) {
      console.warn(`skipping unreadable entry: ${entry.name}`);
      continue;
    }
    throw e;
  }
}

Prevention

When it happens

Trigger: Recursive Deno.copy(fromDir, toDir) where an entry disappears between read_dir and lstat (another process pruning build output), EACCES because a subdirectory lost search permission, ENAMETOOLONG on very deep names, or an NFS stale file handle during the walk.

Common situations: Copying live build output or log directories while another process writes to them; running without read/search permission on part of the tree; network filesystems with flaky attribute replies.

Related errors


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