Skip to content

Fix Deno --global-prefix cache and tool roots (#444) - #1741

Merged
Mikola Lysenko (mikolalysenko) merged 4 commits into
mainfrom
agent/fix-deno-global-prefix-roots
Oct 27, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 4 commits into
mainfrom
agent/fix-deno-global-prefix-roots

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #444

#1719 taught global mode to crawl Deno's npm cache and left two parts of #444 open. This PR closes both.

What changes for users

  1. --global-prefix $DENO_DIR and --global-prefix $DENO_DIR/npm now work. Before, they found nothing (package_not_installed); only the registry dir $DENO_DIR/npm/registry.npmjs.org did. They now expand to every registry dir below them, the same ones -g crawls (jsr.io stays out).
  2. A Deno per-tool bin/.<tool>/node_modules that Deno never loads is refused. On Deno 2.7 and later, a tool installed from a local or JSR entrypoint gets that tree, but its deno.json sets no nodeModulesDir, so Deno loads the $DENO_DIR/npm copy. Pointing --global-prefix at it used to report applied and a VEX not_affected while the tool kept running the unpatched code. It's now a usage error (exit 2) like a nonexistent prefix, and the message points at -g / $DENO_DIR. A prefix inside the decoy, or a symlink to it, is refused too. rollback, remove, vendor --revert and repair still accept it, so files an earlier release patched there can be restored. Trees Deno does load are still accepted: an npm: entrypoint tool ("nodeModulesDir": "manual" + package.json), auto, or any tree with a sibling package.json.

Root cause

NpmCrawler::node_modules_paths_sync read a --global-prefix as either a Deno registry dir or a node_modules-shaped dir. It never expanded a Deno cache parent into its registry dirs, and nothing checked whether Deno would load a per-tool node_modules.

Implementation

  • npm_crawler.rs: split the -g walk into deno_npm_registry_dirs(npm), and add deno_cache_parent_registry_dirs(prefix). It only fires for a prefix named npm, or one with an npm child, that is not a node_modules and has no package.json. So a global node_modules holding the npm CLI keeps its old walk (regression test included).
  • deno_crawler.rs: is_unloaded_deno_tool_node_modules(path) matches only <..>/bin/.<tool>/node_modules with a plain-JSON deno.json/deno.jsonc whose nodeModulesDir is unset, "none" or false, and no package.json. A config that isn't plain JSON (JSONC comments) is not judged, so it fails open to the old behavior rather than refusing a live tree.
  • args.rs: GlobalArgs::validate_paths refuses that prefix next to the existing "does not exist" check, so every command (scan/apply/rollback/vex/vendor/...) refuses it the same way.

No wrapper (npm/, pypi/, gem/) changes are needed: they only dispatch to the binary.

Tests (red → green)

#444 part test without fix with fix
$DENO_DIR / $DENO_DIR/npm prefix crawler_npm_e2e::deno_dir_and_npm_dir_prefixes_expand_to_registry_dirs FAIL (roots = the prefix itself, 0 packages) pass
control: npm package child keeps the walk crawler_npm_e2e::prefix_with_npm_package_child_keeps_node_modules_walk pass pass
per-tool decoy refused on every command cli_path_flags_validated::deno_tool_node_modules_deno_never_loads_is_a_usage_error FAIL (not exit 2) pass
control: live per-tool trees accepted cli_path_flags_validated::deno_tool_node_modules_deno_loads_is_accepted pass pass
detector edge cases deno_crawler::tests::unloaded_deno_tool_node_modules_matches_only_the_decoy n/a (new fn) pass

Local: cargo fmt --all -- --check OK, cargo clippy --workspace --all-features -- -D warnings OK, crawler_npm_e2e 118/118, cli_path_flags_validated 28/28, core deno unit tests 39/39. Full cargo test --workspace --all-features --no-fail-fast: 236 binaries ok. It caught one real issue, fixed in c786cc7: the crawler architecture test requires FIFO-safe reads, so deno.json is now read with read_regular_to_string_sync. The remaining 12 failures are sandbox-only and outside this diff: the sandbox runs as root, so chmod-based write-failure tests can't fail (in_process_redirect write failures, vlt_heal, copy_tree symlinked root, pypi_poetry/pypi_requirements wire failures, repair_* unremovable lock), and e2e_bun_lockb needs patches-api.socket.dev, which the sandbox proxy blocks. CI runs them unprivileged and online.

/code-review high (run on the diff; fixes in a197537)


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
--global-prefix naming $DENO_DIR or $DENO_DIR/npm found nothing:
only the registry dir itself worked. It now expands to every
registry dir below it, the same ones -g crawls.

On Deno 2.7+, a --global-prefix naming a tool's per-tool
bin/.<tool>/node_modules that Deno never loads (its deno.json sets
no nodeModulesDir) reported applied and a VEX not_affected while the
tool kept running the unpatched cache copy. That prefix is now a
usage error (exit 2) pointing at -g or $DENO_DIR. Trees Deno does
load (npm: entrypoint tools, nodeModulesDir auto/manual, or a
package.json) are still accepted.

Fixes #444

Assisted-by: Claude Code:claude-opus-5-5
The crawler architecture test requires project files to be read
through the FIFO-safe reader, so a named pipe at deno.json can't
hang a scan.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 27, 2026 13:33
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Review fixes for the #444 follow-up:
- rollback, remove, vendor --revert and repair still accept a Deno
  tool tree, so files an earlier release patched there can be
  restored.
- An explicit nodeModulesDir "none" or false counts even beside a
  package.json; the config is read as JSONC (comments, trailing
  commas, BOM), as Deno reads it.
- A prefix inside the decoy, or a symlink to it, is refused too.
- A dir named npm that holds a node_modules (npm's global prefix on
  Windows, %APPDATA%\npm) keeps its plain walk instead of being read
  as Deno's cache, and a relative prefix such as "." is resolved
  first.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit a197537. Configure here.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 27, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Ready for review at a197537.

  • CI: 50/50 checks green on the current head (31 success, 19 skipped). No main-wide failures.
  • Bugbot: reviewed a197537, no new issues. No unresolved review threads.
  • Already approved by Tanmay Singla (@Tanmay182003) on this head. Mergeable, no conflicts, no CHANGELOG.md change.
  • Worth a look: the --global-prefix Deno decoy check lives in validate_paths (CLI usage error) rather than the crawler. The PR description explains why.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Ready for review at a197537.

  • CI: 50/50 checks green (31 success, 19 skipped), no conflicts.
  • Bugbot: reviewed a197537 with no new issues. The earlier review on c786cc7 is stale.
  • Already approved on this head.
  • For reviewers: the change is in deno_crawler.rs and npm_crawler.rs (--global-prefix now resolves Deno cache parent dirs and skips Deno's per-tool node_modules trees), plus FIFO-safe reads of Deno tool config.

Generated by Claude Code

Merged via the queue into main with commit ffa6ef8 Oct 27, 2026
50 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-deno-global-prefix-roots branch October 27, 2026 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

3 participants