Repository navigation
Fix .yarnrc read by two drifted tokenizers (#1901) - #1915
Conversation
Assisted-by: Claude Code:claude-opus-5-5
The npm crawler and the hosted redirect each tokenized yarn classic's .yarnrc, and the copies drifted. Given --modules-folder 'deps', the crawler kept the quotes and crawled a folder named 'deps', missing the real install, so its packages were reported as not installed. It also accepted values yarn can't read (~/x, an unquoted C:\deps). Both now call formats::yarn::classic_rc, built from the redirect's yarn-faithful reader. A value followed by anything but a # comment is unreadable to yarn, so it now sets nothing for either caller. Fixes #1901 Assisted-by: Claude Code:claude-opus-5-5
yarn 1.22 reads a single-quoted .yarnrc value only through its YAML fallback, so only in a `key: 'value'` pair. A bare `--modules-folder 'deps'` line is ignored and the install goes to node_modules (checked against yarn 1.22.22). The shared reader now accepts a single-quoted value only after a `:` and whitespace, so neither the crawler nor the redirect roots a crawl at a folder yarn never installs into. Refs #1901 Assisted-by: Claude Code:claude-opus-5-5
yarn 1 parses .yarnrc with its lockfile grammar, and if that fails anywhere in the file, re-reads the whole file as YAML. A line-by-line reader could not model this. `--modules-folder: my deps` installs into `my deps`, while one unreadable line elsewhere (`yarn-path ~/y.js`) makes yarn ignore every setting in the file. formats::yarn::classic_rc now runs yarn's tokenizer and parser over the file and falls back to a YAML read. The crawler's --modules-folder, the redirect's global-folder and the yarn-offline-mirror read all go through it. Every case in the tests was checked against yarn 1.22.22. Refs #1901 Assisted-by: Claude Code:claude-opus-5-5
The public yarnrc docs linked to a crate-private module, which rustdoc reports as a private link. Name it in code instead. Assisted-by: Claude Code:claude-opus-5-5
Review against yarn 1.22's parse.js and js-yaml's FAILSAFE schema found these gaps in the shared reader: - An empty YAML value is null (unset), not the string "~". - A duplicate key or an unknown tag makes yarn reject the file. - An alias reads its anchored value. - Only \n or \r\n starts an indent, and a comment runs to \n. - An empty key after a comma is an error. The offline-mirror read now treats only boolean false as "no mirror": yarn resolves a string "false" as a mirror path. Callers that read several keys parse each file once. Refs #1901 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
Labeled Ready for review by the burn-down agent.
Slack announcement could not be sent in this run (no Slack send tool available); the next run will retry. Generated by Claude Code |
Final review brief (
|
Wenxin Jiang (Wenxin-Jiang)
left a comment
There was a problem hiding this comment.
[agent] Reviewed 11551ef. The shared parser and caller precedence look correct. One low-priority robustness finding is inline; no major correctness issue found. Validation: source/regression-test review and available exact-head CI, plus an optimized standalone probe of the actual parser module.
The .yarnrc parser recursed once per nested block with no limit, so a repository .yarnrc nested a few thousand levels deep overflowed the stack and aborted the whole scan. Past 64 levels the file now reads as unreadable, which is also how yarn's own recursive parser fails on it. Refs #1901 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
This PR moved the crawler's .yarnrc reads into classic_rc, and main dropped the import's other use, so the merged tree failed clippy's unused-import check in the merge queue. Refs #1901 Assisted-by: Claude Code:claude-opus-5-5
|
[agent] Removed from the merge queue because Generated by Claude Code |
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ 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 a8a8aff. Configure here.
|
[final reviewer] Re-enqueued at Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1901
Summary
Yarn classic's
.yarnrcwas read by two tokenizers that had drifted:the npm crawler's
parse_yarnrc_modules_folder/split_yarnrc_token,and the hosted redirect's
yarnrc_setting/yarnrc_token. Theoffline-mirror read was a third variant, using
unquote_rc_value. Allof them now go through one reader,
formats::yarn::classic_rc, whichreads the file the way yarn 1.22 does.
Root cause
Each module tokenized
.yarnrcline by line in its own way. yarn 1actually parses the whole file with its lockfile grammar
(
lockfile/parse.js). If that fails anywhere in the file, yarnre-reads the whole file as YAML (js-yaml
safeLoad, FAILSAFE schema).If YAML fails too, yarn exits with an error. No per-line reader can
model that. For example, one unreadable
yarn-path ~/y.jsline makesyarn ignore
--modules-folder depson the line above.What yarn 1.22.22 actually does
I ran
yarn installwith each.yarnrcbelow and recorded whereis-numberlanded:.yarnrc--modules-folder deps/"deps"/deps # c/--modules-folder:depsdeps--modules-folder: 'deps',"--modules-folder": 'deps',--modules-folder : 'deps'(YAML fallback)deps--modules-folder: my deps(YAML fallback)my deps--modules-folder, --install.modules-folder depsdeps--modules-folder 'deps',--modules-folder:'deps',~/x,C:\deps,C:deps,deps, x,trueDeps, a tab separatornode_modules(not read)--modules-folder deps+yarn-path ~/y.js; odd indentationnode_modules(the file falls to YAML and reads as a plain string)"--modules-folder" 'deps',false,0, a nested blockCorrection to the issue: the issue says the redirect's reader gets
--modules-folder 'deps'right (deps). It doesn't: yarn ignores thatline. Both old readers were wrong for it.
Changes
crates/socket-patch-core/src/formats/yarn/classic_rc.rs(new):lockfile/parse.js:booleans, numbers,
a, bmulti-keys, indent rules, nested blocks,and a lone
\r.serde_saphyrparser already in usefor
.yarnrc.yml. It matches js-yaml FAILSAFE: an empty value isnull, a duplicate key or an unknown tag makes the file unreadable,
and an alias reads its anchored value.
settings/scalar/take_str.npm_crawler::parse_yarnrc_modules_folderis onesettingscall.split_yarnrc_tokenand the crawler's line loop are deleted.patch/redirect/yarnrc.rs:yarnrc_settingandyarnrc_tokenaredeleted.
global-folderandyarn-offline-mirrorread throughclassic_rc. The mirror is disabled only by a booleanfalse,because yarn's
getOfflineMirrorPathchecks=== falseand resolvesa string
"false"as a path.Tests (red → green)
npm_crawler::tests::yarnrc_modules_folder_reads_like_yarn(new) andnpm_crawler::tests::test_yarnrc_single_quoted_modules_folder_is_a_crawl_root(new). The second crawls a real tree:
deps/for--modules-folder: 'deps', and onlynode_modules/for--modules-folder 'deps'.npm_crawler::tests::test_parse_yarnrc_modules_folder: unquotedC:\depsis now unset, matching yarn.formats::yarn::classic_rc::tests(new) pin every table row, plusthe js-yaml and lone-
\rcases.main's reader:cargo test -p socket-patch-core --lib -- yarnrchad 3 failures. Green:cargo test -p socket-patch-core --lib -- classic_rc yarnrc yarn mirrorgives 466 passed. Also green:crawler_npm_e2e(123 passed), plus the yarn and modules-folder casesin
e2e_vex_redirect,e2e_vex_vendorandin_process_alternate_installers(real yarn 1.22.22).cargo fmt --all -- --checkandcargo clippy --workspace --all-features -- -D warningsare clean.cargo test --workspace --all-features --no-fail-fastlocally: everyfailure is environmental and none involves yarn. The container runs as
root, so the permission-based write-failure tests in core lib,
in_process_redirectandrepairdon't fail as intended. The patchAPI is unreachable through the sandbox proxy, and the local bun
version differs from CI's (
e2e_bun_lockb). CI runs these normally..yarnrc, so none needed changes.Per-issue checklist:
formats/yarn/, used by the crawler and byredirect. A single-quoted test, and an untokenizable-value test
ignored by both callers.
Review follow-up
deep_nesting_is_unreadable_not_a_stack_overflow./code-review high
I ran it twice. The second pass was on the whole-file reader. I fixed
every confirmed finding: the YAML null value, duplicate keys, tags,
aliases,
\rhandling, an empty comma key, string"false", doubleparsing, and stale docs. One finding is not fixed here:
.yarnrcis unreadable, or sets anempty, boolean or numeric
--modules-folder, the crawler falls backto an ancestor's value. yarn would error or behave differently. That
is
yarnrc_modules_folder's hierarchy merge, not the tokenizer.Changing it would affect a different code path, which Fix crawlers missing parent dirs under --cwd . (#1902) #1906 also
touches. Left as a follow-up.
https://claude.ai/code/session_011f9aeSZeF6iZU51H3F7GgF
Note
Medium Risk
Changes how untrusted project
.yarnrcfiles drive npm crawl roots and yarn redirect paths (global folder / offline mirror), with behavior shifts for edge-case syntax; mitigated by yarn-parity tests and nesting limits.Overview
Fixes #1901 by replacing three ad-hoc
.yarnrcline parsers with a singleformats::yarn::classic_rcmodule that mirrors yarn 1.22: lockfile grammar first, YAML fallback when the lockfile parse fails anywhere, and no settings when both fail.The npm crawler now derives
--modules-folder/--install.modules-folderfrom that reader (e.g.key: 'deps'works via YAML;key 'deps',~/x, and unquotedC:\depsare unset like yarn). The hosted redirect uses the same reader foryarn-offline-mirror(only booleanfalsedisables the mirror) andglobal-folder. Inline tokenizers in the crawler and redirect are removed.The new parser includes a 64-level nesting cap on untrusted
.yarnrcfiles and extensive regression tests, including an async crawl that roots atdeps/only when yarn would.Reviewed by Cursor Bugbot for commit a8a8aff. Configure here.
Generated by Claude Code