You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Injected copy: sync materializes a symlinked directory that the install keeps as a symlink #147
A fresh install of an injected package that contains an internal directory symlink (link-to-a-dir -> files/foo) keeps it as a symlink in the copy, in both v11 and v12. The install's directory fetch reads with preserve_symlinks: true in v12 and resolveSymlinks: false in v11. The sync after a project's scripts reads the source without preserving symlinks, so the copy ends up with a real link-to-a-dir/ directory holding hardlinks of the files. pnpm/pnpm#17033 keeps that existing sync behavior: it expands a symlinked directory the packlist lists as one entry.
To match the install, the syncers would read with the install's symlink policy, and the dir patchers (injected-deps-syncerdir_patcher.rs, v11 DirPatcher.ts) would treat a symlink as a link (lstat), not follow it. That applies to publish_edits / publishEditsForWatchers too, which copy files. The pnpm v11 e2e test "directories and symlinks" in pnpm11/pnpm/test/syncInjectedDepsAfterScripts.ts covers the current behavior.
Written by an agent (Claude Code, claude-opus-5-5).
Another case for this: within one walk, the v12 directory walker visits a directory once per canonical path (the visited set in walk_all_inner). Two links to the same target inside a package (a -> ../shared, b -> ../shared) therefore give the injected copy the files under only one of them. Raised in review on pnpm/pnpm#17033.
Written by an agent (Claude Code, claude-opus-5-5).
One more case, raised by Qodo on pnpm/pnpm#17033: v11's walk of every file (_fetchAllFilesFromDir) has no cycle guard. A link inside a package such as loop -> . makes it recurse through loop/loop/... until the path is too long. This affects the install with deployAllFiles and the sync of an injected copy. v12's walker has a visited set.
Written by an agent (Claude Code, claude-opus-5-5).
Reproduced on main (ebbb1b3e1a) with both CLIs. app injects lib, the lockfile is shared, and syncInjectedDepsAfterScripts: [build] is set. Each cell shows what app's copy holds after the install, then after pnpm run build in lib. @ marks a symlink.
lib holds
deployAllFiles
v12
v11
link-to-a-dir -> files/foo
false
link kept, then kept
link kept, then kept
link-to-a-dir -> files/foo
true
link kept, then kept
materialized by the install
a -> shared, b -> shared
false
links kept
install OK, sync fails with EEXIST (it links lib/a/x.txt over the copy's a/x.txt, which is shared/x.txt through the kept link)
a -> shared, b -> shared
true
links kept
materialized
loop -> .
false
link kept
install OK, sync fails with ELOOP (the walk of the copy follows loop/loop/...)
loop -> .
true
link kept
install fails with ELOOP in _fetchAllFilesFromDir
build adds newlink -> files/foo
false
materialized by the sync
materialized by the sync
build adds newlink -> files/foo
true
missing after the sync
materialized, as its install does
So:
v12's install keeps every link. Its sync turns a new directory link into a real directory, and with deployAllFiles it drops the new link entirely. The two-links walker case from the earlier comment did not show up here, because the install reads with preserve_symlinks.
v11's sync crashes on a copy that holds a link to an ancestor or two links to one directory. Its walk of every file crashes on loop -> ., which breaks the install under deployAllFiles.
Under deployAllFiles, v11's install materializes directory links and v12's keeps them.
To make the sync write what the install writes, it would read both trees with the install's symlink policy and recreate links the way the importer does. The importer's rules live in deps-restorer (placement/symlinks.rs): it rejects targets outside the package, rewrites absolute targets as relative, recreates a directory link only when its target is imported, imports a file link as a file, and uses junction targets on Windows. The syncer should share them rather than copy them.
Written by an agent (Claude Code, claude-opus-5-5).
The fix is in pnpm/pnpm#17088, for both CLIs. Each CLI's sync now writes a package's links to its own files the way its install does, by the importer's rules, and the v11 crashes are fixed. v11's install under deployAllFiles still materializes directory links, where v12 keeps them. That difference is left as it was.
Written by an agent (Claude Code, claude-opus-5-5).
Fixed by pnpm/pnpm#17088, now merged (453870cc3f). In both CLIs, the sync after a listed script now writes a package's links to its own files the way the install does. The v11 EEXIST and ELOOP failures are fixed, and so is the v11 install failure on a looping link under deployAllFiles.
One difference is left as decided: v11's install under deployAllFiles still materializes directory links, where v12 keeps them.
Written by an agent (Claude Code, claude-opus-5-5).
Activity
zkochan commentedon Oct 17, 2026
Another case for this: within one walk, the v12 directory walker visits a directory once per canonical path (the
visitedset inwalk_all_inner). Two links to the same target inside a package (a -> ../shared,b -> ../shared) therefore give the injected copy the files under only one of them. Raised in review on pnpm/pnpm#17033.Written by an agent (Claude Code, claude-opus-5-5).
zkochan commentedon Oct 17, 2026
One more case, raised by Qodo on pnpm/pnpm#17033: v11's walk of every file (
_fetchAllFilesFromDir) has no cycle guard. A link inside a package such asloop -> .makes it recurse throughloop/loop/...until the path is too long. This affects the install withdeployAllFilesand the sync of an injected copy. v12's walker has avisitedset.Written by an agent (Claude Code, claude-opus-5-5).
zkochan commentedon Oct 18, 2026
Reproduced on main (ebbb1b3e1a) with both CLIs.
appinjectslib, the lockfile is shared, andsyncInjectedDepsAfterScripts: [build]is set. Each cell shows whatapp's copy holds after the install, then afterpnpm run buildinlib.@marks a symlink.libholdsdeployAllFileslink-to-a-dir -> files/foolink-to-a-dir -> files/fooa -> shared,b -> sharedlib/a/x.txtover the copy'sa/x.txt, which isshared/x.txtthrough the kept link)a -> shared,b -> sharedloop -> .loop/loop/...)loop -> ._fetchAllFilesFromDirnewlink -> files/foonewlink -> files/fooSo:
deployAllFilesit drops the new link entirely. The two-links walker case from the earlier comment did not show up here, because the install reads withpreserve_symlinks.loop -> ., which breaks the install underdeployAllFiles.deployAllFiles, v11's install materializes directory links and v12's keeps them.To make the sync write what the install writes, it would read both trees with the install's symlink policy and recreate links the way the importer does. The importer's rules live in
deps-restorer(placement/symlinks.rs): it rejects targets outside the package, rewrites absolute targets as relative, recreates a directory link only when its target is imported, imports a file link as a file, and uses junction targets on Windows. The syncer should share them rather than copy them.Written by an agent (Claude Code, claude-opus-5-5).
zkochan commentedon Oct 18, 2026
The fix is in pnpm/pnpm#17088, for both CLIs. Each CLI's sync now writes a package's links to its own files the way its install does, by the importer's rules, and the v11 crashes are fixed. v11's install under
deployAllFilesstill materializes directory links, where v12 keeps them. That difference is left as it was.Written by an agent (Claude Code, claude-opus-5-5).
fix: keep a package's links to its own files in synced injected copies (
zkochan commentedon Oct 18, 2026
Fixed by pnpm/pnpm#17088, now merged (453870cc3f). In both CLIs, the sync after a listed script now writes a package's links to its own files the way the install does. The v11 EEXIST and ELOOP failures are fixed, and so is the v11 install failure on a looping link under
deployAllFiles.One difference is left as decided: v11's install under
deployAllFilesstill materializes directory links, where v12 keeps them.Written by an agent (Claude Code, claude-opus-5-5).