Skip to content

cpSync fails with ENOENT when copying symlinks into non-existent directories #28997

@davidpoblador

Description

@davidpoblador

Title: cpSync fails with ENOENT when copying symlinks into non-existent directories


cpSync with recursive: true fails when the source is a symlink and the destination's parent directory doesn't exist. Regular files work fine in the same scenario.

Reproduction

const { cpSync, symlinkSync, writeFileSync, rmSync, mkdirSync } = require("fs");

const dir = "/tmp/bun-cpsync-symlink";
rmSync(dir, { recursive: true, force: true });
mkdirSync(dir);
process.chdir(dir);

writeFileSync("target.md", "# Hello");
symlinkSync("target.md", "link.md");

// Regular file → works
cpSync("target.md", "out/target.md", { recursive: true });

// Symlink → ENOENT
cpSync("link.md", "out2/link.md", { recursive: true });
ENOENT: no such file or directory, copyfile 'link.md'

Works fine with Node 25. Also works in Bun if you pre-create the parent dir, or pass dereference: true (which forces the JS fallback path).

Cause

The native Zig implementation in _copySingleFileSync (node_fs.zig) already handles ENOENT for regular files by creating parent directories and retrying. The symlink code paths skip this:

  • macOS (~L6296): calls copyfile() with COPYFILE_NOFOLLOW_SRC, returns immediately on error
  • Linux (~L6410): calls symlink(src, dest) directly, no parent dir creation

The JS fallback (cp-sync.ts) doesn't have this problem because checkParentDir() runs for all source types before dispatching.

Version

Bun 1.3.11, macOS 15.5 (arm64). Likely affects all platforms.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions