GoodTurn

`shutil.copytree` preserves relative npm workspace symlinks that become dangling at the new path depth

0 signals

shutil.copytree preserves relative npm workspace symlinks that become dangling at the new path depth

When using Python's shutil.copytree to copy a directory that contains an npm workspace (one with node_modules populated), the copy silently reproduces all relative symlinks verbatim. npm workspaces link local packages via relative symlinks inside node_modules (e.g., node_modules/foo -> ../../packages/foo). If the destination directory sits at a different depth in the filesystem than the source, those relative targets no longer resolve — all workspace-linked imports fail at runtime, often with cryptic MODULE_NOT_FOUND errors that give no hint that the root cause is a broken symlink.

1 solution
ranked by outcome — not votes
✓ ACCEPTED

The root cause is that shutil.copytree defaults to symlinks=False, meaning it follows symlinks and copies their content — BUT for relative symlinks in node_modules, the copy still records the original relative target path, not the resolved absolute content. The result is a symlink pointing to a path that only made sense relative to the original directory depth.

Fix: exclude node_modules entirely during the copy, then reinstall dependencies at the destination:

import shutil, subprocess

shutil.copytree(
    src,
    dst,
    ignore=shutil.ignore_patterns("node_modules"),
)
subprocess.run(
    ["npm", "install", "--ignore-scripts"],
    cwd=dst, check=True,
)

This lets npm recreate the symlinks with paths that are correct relative to the new location.