Skip to content

Respect overwrite options when copying symlinks - #1084

Open
jakezwang wants to merge 1 commit into
jprichardson:masterfrom
jakezwang:fix-symlink-copy-overwrite
Open

jakezwang wants to merge 1 commit into
jprichardson:masterfrom
jakezwang:fix-symlink-copy-overwrite

Conversation

@jakezwang

@jakezwang jakezwang commented Sep 19, 2026 •

Copy link
Copy Markdown

Copying a symlink currently replaces an existing destination symlink even with overwrite: false or clobber: false. It also succeeds with overwrite: false, errorOnExist: true instead of reporting the existing destination.

Apply the existing overwrite checks in the symlink branches of copy and copySync, before unlinking the destination. Tests cover valid and broken destination links, both overwrite options, errorOnExist, default replacement and missing destinations.

The new tests reproduce 12 failures before the fix. npm test passes on Node 20.20.2, 22.23.2 and 24.4.1: lint, 755 unit tests and ESM tests. Eight existing tests remain pending on macOS; cross-device and other platform tests were not run locally.

Compatibility: the maintainer noted that Node fs.cp() currently has the same symlink-overwrite behavior and asked to keep fs-extra aligned with it. This PR therefore awaits an upstream compatibility decision; it is not yet ready to merge solely on the basis of the passing tests. See the maintainer discussion at #1084 (comment).

Upstream tracking: nodejs/node#66774, opened manually by the author.

Implementation and validation used AI assistance.

@RyanZim

RyanZim commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator

Of note, fs.cp() has the same behavior, and we would like to stay in sync with that: #912 (comment). So this should be potentially raised with Node itself.

@jakezwang

Copy link
Copy Markdown
Author

Understood. This was independently found edge-case behavior rather than a production incident. I have not opened an issue in Node, and I have not changed the proposed behavior further. Keeping fs-extra aligned with fs.cp() is a design constraint; this needs an upstream decision before treating the PR as ready to merge.

@jakezwang

Copy link
Copy Markdown
Author

I checked the Node compatibility constraint against Node 22.23.3, 24.21.0, and 26.10.0 on macOS arm64. With valid destination symlinks, all three fs.cp, fsPromises.cp, and fs.cpSync APIs replace the link under { force: false }, and also replace it without an error under { force: false, errorOnExist: true }. The two async APIs do the same for broken destination links. Ordinary-file controls preserve the destination, and errorOnExist: true raises ERR_FS_CP_EEXIST as expected. I have not counted broken-link cpSync cases: those abort in macOS's std::filesystem::equivalent before a JavaScript exception can be caught.

Here is a dependency-free reproduction for the valid-link case; run it with node repro.cjs:

const fs = require('node:fs/promises');
const os = require('node:os');
const path = require('node:path');
(async () => {
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'cp-symlink-repro-'));
  try {
    const src = path.join(dir, 'src'), dest = path.join(dir, 'dest');
    const a = path.join(dir, 'a'), b = path.join(dir, 'b');
    await fs.writeFile(a, 'source');
    await fs.writeFile(b, 'destination');
    await fs.symlink(a, src);
    await fs.symlink(b, dest);
    await fs.cp(src, dest, { force: false, errorOnExist: true });
    console.log((await fs.readlink(dest)) === b); // false; no error thrown
  } finally { await fs.rm(dir, { recursive: true, force: true }); }
})().catch(console.error);

This is still an independently found edge case, not a production incident. Keeping fs-extra aligned with Node remains the unresolved design decision, so I have not changed this PR further. I prepared the reproduction and Node issue facts, but have not opened an automated Node issue: their contribution rules and AI communication guidelines require a human-owned report rather than an entirely AI-generated posting. The investigation above was assisted by OpenAI Codex.

Executed reproduction sources and raw results are available for independent verification.

@RyanZim

RyanZim commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator

@jakezwang (the actual human Jake, not the AI posting in his name): if you care about this, I would suggest you personally review this and manually open the issue with Node.js in accordance with their AI policy.

@jakezwang

Copy link
Copy Markdown
Author

@jakezwang (the actual human Jake, not the AI posting in his name): if you care about this, I would suggest you personally review this and manually open the issue with Node.js in accordance with their AI policy.

yes this is human jake speaking. i will open the issue manually

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants