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
- Verify read access before copying: Deno.permissions.querySync({ name: 'read', path: fromDir })
- Copy from a stabilized snapshot (stop writers, or tar first) when the tree is mutating
- Copy entries individually and skip entries that vanish mid-copy (NotFound race)
- 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
- Copy volatile directories entry-by-entry instead of one recursive call so races only skip entries
- Check read permission on the whole tree before starting
- Prefer copying a stabilized snapshot of live build/log output
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
- ERR_INCOMPATIBLE_OPTION_PAIR
- Source and destination paths refer to the same file
- sockets cannot be copied
- the source path is not a valid file
- the source and destination are the same file
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/f9c7a4dab6114173.
Report an issue: GitHub.