Skip to content

fix(resolver): resolve nested link:/file: deps from local parents and overrides#470

Merged
jdx merged 5 commits intomainfrom
claude/happy-goodall-afe73b
May 2, 2026
Merged

fix(resolver): resolve nested link:/file: deps from local parents and overrides#470
jdx merged 5 commits intomainfrom
claude/happy-goodall-afe73b

Conversation

@jdx
Copy link
Copy Markdown
Contributor

@jdx jdx commented May 2, 2026

Summary

Fixes the install-time error:

× failed to resolve dependencies
  ╰─▶ registry error for @scope/foo: transitive local specifier
      link:./libs/foo cannot be resolved without the parent
      package source root

Two cases that previously bailed:

  • file:/link: parent declares a transitive link: in its own package.json. The resolver now anchors the relative path on the parent's source root (already on the parent's LockedPackage.local_source) instead of the importer dir.
  • Root pnpm.overrides rewriting a registry dep to link:./libs/foo. Override paths are anchored at the project root regardless of which workspace package consumes them — mirrors pnpm. The resolver tags override-substituted local specs and routes them through project_root rather than the consumer's importer dir.

Linker side, transitive link: deps from a file:/link: parent get no .aube/<name>@link+... entry of their own; the parent's sibling symlink points straight at the on-disk target, matching how root-level link: deps already work.

Test plan

  • Two new bats cases in test/local_deps.bats — one for the parent-anchored path, one for the override-anchored path.
  • Existing local_deps.bats keeps passing (file:/link:/tarball + workspace importer + roundtrip + excludeLinksFromLockfile).
  • cargo test, cargo clippy --all-targets -- -D warnings, cargo fmt --check clean.
  • No new failures elsewhere in the bats suite (regressions checked against main).

🤖 Generated with Claude Code


Note

Medium Risk
Touches dependency resolution and virtual-store materialization for link:/file: specs, which can affect install correctness and on-disk symlink layout across workspaces and the global virtual store. Changes are localized and covered by new bats tests, but regressions could break installs for edge-case graphs.

Overview
Fixes resolution and linking of nested link:/file: dependencies.

The resolver now (1) anchors override-rewritten local specs to the project root via a new range_from_override flag, and (2) allows transitive local specs to resolve relative to a local (file:/link:) parent’s source root while keeping registry-parent exotic subdeps blocked.

The linker threads a precomputed dep_path -> absolute target map for link: packages through materialize_into/ensure_in_virtual_store so transitive link: deps create sibling symlinks directly to the on-disk target (avoiding dangling .aube/<name>@link+... paths), including during GVS prewarming. New local_deps.bats cases cover override anchoring, GVS behavior, and parent-root anchoring.

Reviewed by Cursor Bugbot for commit 0d293c9. Bugbot is set up for automated code reviews on this repo. Configure here.

… overrides

Two cases that previously failed install with "transitive local
specifier ... cannot be resolved without the parent package source
root":

1. A `file:`/`link:` parent declares a transitive `link:./libs/foo`
   in its own package.json. The resolver now anchors the relative
   path on the parent's source root (already tracked on the parent's
   `LockedPackage.local_source`) instead of the importer dir.

2. Root `pnpm.overrides` rewriting a registry dep to `link:./libs/foo`.
   Override paths are anchored at the project root regardless of which
   workspace package consumes them — mirrors pnpm. The resolver now
   marks override-substituted local specs and routes them through
   project_root rather than the consumer's importer dir.

Linker side, transitive `link:` deps from a `file:`/`link:` parent get
no `.aube/<name>@link+...` entry of their own; the parent's sibling
symlink points straight at the on-disk target, matching how root-level
`link:` deps work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 2, 2026

Greptile Summary

This PR fixes two resolver/linker failures for nested link:/file: specs: (1) a new range_from_override flag causes override-substituted local specs to anchor at the project root and bypass the block_exotic_subdeps guard, and (2) transitive link: deps from a file:/link: parent now anchor against the parent's on-disk source root. The linker side avoids phantom .aube/<dep>@link+... entries by pre-computing a dep_path → absolute target map and symlinking sibling deps directly, including in the global virtual store prewarm path.

Confidence Score: 4/5

Safe to merge; two P2 observations but no blocking bugs found.

Changes are well-scoped, thoroughly commented, and covered by four new integration tests. The previous-thread P1 (override blocked by exotic-subdep guard) is correctly fixed. The two P2 notes — tarball parent limitation undocumented and GVS test gated behind an external registry — do not affect correctness. No P0/P1 issues found.

crates/aube-resolver/src/resolve.rs (parent_source_root/importer_root logic) and the GVS prewarm path in crates/aube/src/commands/install/mod.rs deserve careful reading.

Important Files Changed

Filename Overview
crates/aube-resolver/src/resolve.rs Core resolver fix: adds range_from_override guard to bypass should_block_exotic_subdep and anchors override-specified paths at project root, while anchoring transitive local specs at parent's source root for Directory/Link parents.
crates/aube-linker/src/lib.rs Linker fix: introduces build_nested_link_targets (dep_path → absolute path map for LocalSource::Link entries) and threads it through materialize_into/ensure_in_virtual_store so sibling symlinks for link: transitives point directly at the on-disk target instead of a phantom .aube/<dep>@link+... entry.
crates/aube-resolver/src/lib.rs Adds range_from_override: bool field to ResolveTask with false default in both constructors; no logic changes.
crates/aube-resolver/src/tests.rs Mechanical addition of range_from_override: false to test fixture structs; no behavioral changes to existing tests.
crates/aube/src/commands/install/mod.rs GVS prewarm path: captures materialize_cwd, pre-computes nested_link_targets via build_nested_link_targets, wraps in Arc, and threads it through the per-package ensure_in_virtual_store spawn_blocking calls.
test/local_deps.bats Adds four integration tests: override-to-link project-root anchoring, GVS sibling symlink chain with override (requires AUBE_TEST_REGISTRY), registry-parent transitive override, and parent-source-root anchored transitive link:.

Fix All in Claude Code

Reviews (5): Last reviewed commit: "fix(linker): use absolute targets for ne..." | Re-trigger Greptile

Comment thread crates/aube-resolver/src/resolve.rs Outdated
Greptile catch on PR #470. When `block_exotic_subdeps` is on (the
default) and a registry parent's transitive dep is rewritten by a root
`pnpm.overrides` entry to `link:./libs/foo`, the guard fired before the
override-aware `importer_root` logic ever ran — install errored with
`BlockedExoticSubdep` even though the override was a deliberate
root-declared opt-in.

The original test only covered the workspace-pkg consumer (a local
source, where the guard is already a no-op). Add a regression test that
sticks the override on a transitive of a `file:` tarball parent so the
guard's default-on path is exercised.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread crates/aube-linker/src/lib.rs Outdated
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

Benchmark changes

Public ratios: warm installs vs Bun 4x -> 7x; warm installs vs pnpm 9x -> 11x.

Benchmark aube bun pnpm
Fresh install (warm cache) 246ms -> 260ms (+6%) 1011ms -> 1864ms (+84%) 2227ms -> 2927ms (+31%)
CI install (warm cache, GVS disabled) 1174ms -> 930ms (-21%) 1530ms -> 1856ms (+21%) 2049ms -> 2366ms (+15%)
CI install (cold cache, GVS disabled) 4273ms -> 4276ms (+0%) 4034ms -> 4319ms (+7%) 4940ms -> 5859ms (+19%)

0d293c9 vs 6e64b72 | aube/bun/pnpm | 3 scenarios | 3 runs | 500mbit/50ms | generated by Codex.

jdx and others added 2 commits May 2, 2026 13:32
Cursor catch on PR #470. With `block_exotic_subdeps` bypassed for
overrides (prior commit), an override that retargets a registry
parent's transitive to `link:./libs/foo` finally reaches the linker —
but `ensure_in_virtual_store` (the GVS materialize entry point) was
passing `None` for the nested-link map, so the parent's sibling
symlink dangled into a non-existent `.aube/<name>@link+...`. GVS is on
by default outside CI, so this hit the user's actual scenario.

Build the map once at the install prewarm site and at
`link_all`/`link_workspace`'s GVS step, share via Arc to the
spawn_blocking workers, and pass it through `ensure_in_virtual_store`.
`build_nested_link_targets` is now `pub` so install.rs can construct
the map alongside `Arc<Linker>`.

Add a bats regression that runs against Verdaccio: tagged with
AUBE_TEST_REGISTRY so it skips when the test registry isn't up, but
exercises the GVS code path end-to-end (registry parent + root
override → link transitive). Verified fails without the linker fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI Linux failure: the previous assertion walked the symlink chain to
the on-disk target, which broke on Linux because `pathdiff` from the
GVS tmp location plus the post-rename clamp produced one extra `..` —
fine on macOS where `/tmp` resolves through `/private/tmp`, broken on
Linux where the over-walk lands at `/`.

Switch the assertion to inspect the symlink target *string* directly:
- must end with `libs/is-number` (the override target)
- must NOT contain `@link+` (the dangling-virtual-store-entry signature
  the prior commit fixed)

Verified: still fails when the linker fix is reverted, target then
shows `is-number@link+<hash>/node_modules/is-number`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit feba62b. Configure here.

Comment thread crates/aube-linker/src/lib.rs
Cursor catch on PR #470. The relative `pathdiff` for nested-link
sibling symlinks didn't account for the GVS tmp→final rename: the
link is created with `tmp_base/<subdir>/node_modules/` as its parent,
then the subdir is atomically moved out of `tmp_base` to the final
`virtual_store/<subdir>/`, leaving the stored relative path one
component too long. POSIX `..`-clamping at `/` happens to mask the
bug for shallow vstore paths on macOS (and on macOS specifically the
`/tmp`→`/private/tmp` expansion gives a different lexical/canonical
depth, which masks it again differently), but Linux walks the over-
counted `..` against the real path and lands on a phantom directory.

Storing the absolute target sidesteps both pitfalls. Sibling symlinks
get away with relative paths because both endpoints live inside
`base_dir` and move together under the rename — but nested-link
targets point at `project_dir/...`, an external path that doesn't
move, so neither rename invariance nor `..`-clamping applies. Windows
already uses absolute targets for siblings under the same rename
pattern.

Strengthen the bats test to chase the symlink chain to the real file
(catches the Linux off-by-one cleanly; macOS would mask it via
`..`-clamp at `/` either way).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx jdx merged commit 7d7ded0 into main May 2, 2026
18 checks passed
@jdx jdx deleted the claude/happy-goodall-afe73b branch May 2, 2026 19:24
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.

1 participant