Skip to content

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

Closed

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.

Activity

  1. robobun commented on Apr 8, 2026

    @robobun
    Collaborator

    🔄 Rebased onto main. Main refactored the Linux symlink path into a _cpSymlink helper (that resolves relative link targets against dirname(src), matching Node) and added a FreeBSD path using the same helper. The rebase conflict was in node_fs.zig; resolved by wrapping the _cpSymlink calls on Linux and FreeBSD with the same ENOENT → makePath → retry fallback I already had for the macOS copyfile and Windows CreateSymbolicLinkW branches.

    Gate:

    • stashed (base commit, no fix): 0 pass / 2 fail, ENOENT: no such file or directory, symlink at the cpSync calls
    • unstashed (with fix): 2 pass / 0 fail

    Force-pushed 4068bc0013.

  2. added a commit that references this issue on May 3, 2026
    4068bc0
  3. added a commit that references this issue on May 4, 2026
    9759b6d
  4. davidpoblador commented on May 13, 2026

    @davidpoblador
    Author

    v1.3.14 (released 2026-05-12) appears to ship fixes for both cpSync issues on Linux/FreeBSD — the symlink target resolution and the ENOENT when copying a symlink to a destination whose parent directory doesn't exist (https://bun.com/blog/bun-v1.3.14). Can this be closed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions