fix(scan): surface yarn PnP refusal instead of silent success-0 across all modes - #201
fix(scan): surface yarn PnP refusal instead of silent success-0 across all modes#201Mikola Lysenko (mikolalysenko) wants to merge 2 commits into
Conversation
…efusal On any yarn Plug'n'Play project, all three scan modes exit 0 / status=success / scannedPackages=0 with no warning (the crawler leg is empty because node_modules is absent, and the lockfile-supplement leg swallows the flavor probe's PnP diagnosis), and apply exits 0 with the calm noManifest status because the documented yarn_pnp_unsupported refusal sits below the noManifest early-return — unreachable when scan never wrote a manifest. These tests fail on current main (7 failed / 20 passed / 3 ignored): - scan --json --yes --mode agent|hosted|vendored on a PnP fixture must surface an explicit warnings[] refusal (exit semantics deliberately unchanged) and touch no file - human-mode scan must print the refusal to stderr - the pnpm node-linker=pnp twin surfaces its own diagnosis - apply --json / human with NO manifest must refuse loudly with the existing yarn_pnp_unsupported envelope, not silent noManifest Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s all modes Root cause (verified on da58dee): - vendor/npm_flavor.rs detect_npm_lock_flavor correctly diagnoses PnP loaders (vendor_yarn_berry_unsupported / vendor_pnpm_pnp_unsupported) before any lockfile sniff, but lock_inventory.rs inventory_npm_lock matched Err(_) and fell through to the Rush fallback returning None, DISCARDING the diagnosis — so scan's lockfile-supplement leg came back empty with no warnings channel. - Under PnP node_modules/ is absent, so the crawler leg is also empty; scan hit the package_count==0 early-return and printed status=success / scannedPackages=0 in all three modes (hosted: plus an empty redirect block). - apply's YarnBerryPnP check sat BELOW the noManifest early-return, so the documented loud yarn_pnp_unsupported refusal was unreachable when scan (which cannot discover PnP packages) never wrote a manifest. Fix: - core: inventory_npm_lock now returns Result<Option<..>, UnsupportedNpmLayout>, propagating the two PnP diagnosis codes; every other probe error keeps the Rush fallback and the calm Ok(None). New inventory_project_diagnosed exposes the refusals; inventory_project keeps its signature. - scan: an additive top-level warnings[] ({code, detail}, omitted when empty) rides both the empty-discovery and the non-empty (polyglot) JSON envelopes, plus a stderr 'Warning (<code>): ...' line on the human path. Codes: yarn_pnp_unsupported (same vocabulary as apply's refusal, remedy 'yarn patch <pkg>') and pnpm_pnp_unsupported (the node-linker=pnp twin, pnpm remedies). Exit code and status field are DELIBERATELY unchanged (exit 0 / success — same posture as hosted refusals, which exit 0 with redirected=0). - apply: the YarnBerryPnP refusal is hoisted above the noManifest early-return (extracted into refuse_yarn_pnp, same envelope + exit 1 as the with-manifest gate). CLI_CONTRACT.md documents the carve-out. Tests: the previous commit's 7 RED e2e tests now pass (scan agent/ hosted/vendored JSON warning + no-mutation, human stderr, pnpm twin, apply no-manifest JSON + human refusal); the lock_inventory unit test that pinned the swallow now asserts the propagated diagnosis. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issues.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit cd894fe. Configure here.
| // yarn-PnP silent success-0 no-op. | ||
| if !layout_refusals.is_empty() { | ||
| result["warnings"] = layout_refusal_json(&layout_refusals); | ||
| } |
There was a problem hiding this comment.
Conflicting install hint after PnP
Low Severity
On an empty human-mode yarn PnP scan, the new yarn_pnp_unsupported warning is followed by No packages found. Run … install first. Running install does not create a crawlable node_modules/ under Plug'n'Play, so that second message conflicts with the refusal's yarn patch remedy and can send users down a dead end.
Reviewed by Cursor Bugbot for commit cd894fe. Configure here.
| #[test] | ||
| fn apply_without_manifest_on_pnp_project_refuses_in_human_mode() { | ||
| let dir = tempfile::tempdir().unwrap(); | ||
| make_yarn_berry_project(dir.path()); |
There was a problem hiding this comment.
Bare unwraps in new tests
Low Severity
New PnP e2e helpers and cases use bare .unwrap() on tempdir, fixture writes, and JSON field access. Team guidance for new test code prefers .expect("…") with a step-specific message so CI failures name the setup or assertion that broke instead of a generic unwrap panic.
Additional Locations (1)
Triggered by learned rule: Prefer .expect("context") over bare .unwrap() in test code
Reviewed by Cursor Bugbot for commit cd894fe. Configure here.


P0: yarn Plug'n'Play projects silently no-op in every mode
On any yarn PnP project (yarn 2/3/4, plain PnP and zero-install), all three modes silently did nothing:
scan --json --yes --mode agent|hosted|vendoredexited 0 withstatus: "success",scannedPackages: 0, no warnings (hosted additionally printed an emptyredirectblock). The user believed they were protected; nothing was checked.apply --jsonexited 0 withstatus: "noManifest"— the documented loud refusal (errorCode: yarn_pnp_unsupported, pinned bytests/e2e_safety_yarn_pnp.rs) was unreachable, because scan (which cannot discover PnP packages) never writes a manifest.Matrix evidence (adversarially verified, 11 cells, one root cause)
Empirically reproduced with real production patch data on yarn 2.4.3 / 3.8.7 / 4.6.0 across agent/hosted/vendored × plain-PnP/zero-install: every PnP cell was a silent success-0 no-op +
applynoManifest. The 18 supported (node-modules linker) cells passed, and yarn 2/3 nm refusal paths were clean — this is PnP-specific, one root cause. Intact repro dirs live underworktrees/yarn-matrix-e2e/target/matrix/(e.g.y4-agent/y4-agent-pnp,y4-hosted/y4-hosted-zero).Root cause (at da58dee)
crates/socket-patch-core/src/vendor/npm_flavor.rs:125-149—detect_npm_lock_flavorcorrectly diagnoses PnP via the.pnp.*markers (vendor_yarn_berry_unsupported/vendor_pnpm_pnp_unsupported) before any lockfile sniff.crates/socket-patch-core/src/vendor/lock_inventory.rs:109-115—inventory_npm_lockmatchedErr(_)and fell through to the Rush fallback returningNone, discarding the diagnosis, so the lockfile-supplement discovery leg (scan/discovery.rs) returned empty with no warnings channel.node_modules/is absent, so the crawler leg is also empty; scan hit thepackage_count == 0early-return (scan/mod.rs) and printed success before any mode-specific gate ran.apply.rs,detect_npm_pkg_manager→YarnBerryPnP) sat below the noManifest early-return, so it never fired without a manifest.Fix
inventory_npm_locknow returnsResult<Option<…>, UnsupportedNpmLayout>, propagating the two PnP diagnosis codes instead of conflating "unsupported layout" with "no lockfile". Every other probe error (missing lock, bun.lockb, unsupported versions) keeps the Rush fallback and the calmOk(None). Newinventory_project_diagnosedexposes the refusals;inventory_projectkeeps its signature (vendor/repair callers unchanged).warnings[]({code, detail}objects, omitted when empty) on both the empty-discovery and the non-empty (polyglot) JSON envelopes, plus a stderrWarning (<code>): …line on the human path — in all three modes. Codes:yarn_pnp_unsupported(one vocabulary with apply's refusal; remedyyarn patch <pkg>) andpnpm_pnp_unsupported(thenode-linker=pnptwin, surfaced through the same channel).YarnBerryPnPrefusal is hoisted above the noManifest early-return (extracted intorefuse_yarn_pnp; identical envelope + exit 1 as the with-manifest gate), so a PnP checkout refuses loudly even when no manifest exists.warnings[]channel and the deliberate noManifest carve-out.Exit semantics — deliberately conservative
statusunchanged (exit 0 /success+ warnings entry). Precedent: hosted refusals already exit 0 withredirected: 0(known, deliberately-unchanged residual from the pnpm sweep). The warning is the machine-readable signal that nothing was checked.yarn_pnp_unsupportedsemantics (exit 1) — now also on the no-manifest path. Note: a PnP project with an install hook and no manifest previously "passed" installs silently; it now fails them, exactly as it already did once a manifest existed.Tests (TDD, RED first — see commit order)
7e442ba) adds 7 e2e tests that fail on main (7 failed / 20 passed / 3 ignored): scan agent/hosted/vendored JSON warning + whole-tree no-mutation snapshot, human-mode stderr, the pnpm twin, apply no-manifest JSON + human refusal.lock_inventoryunit test that pinned the swallow (unsupported_flavors_yield_none) intopnp_layouts_propagate_the_diagnosis_instead_of_yielding_none(PnP → propagated diagnosis; genuinely-None cases kept). The unit rewrite rides the fix commit because the new assertion cannot compile against the old signature.cargo test -p socket-patch-core(22 binaries, 0 failures incl. 2265 lib tests), full defaultcargo test -p socket-patch-cli(0 failures;e2e_safety_yarn_pnp27 passed),cargo clippy --workspace --all-features -- -D warningsclean. Formatted only touched files (nocargo fmt --all).y4-agent-pnp): old binary → silent success-0 + noManifest exit 0; new binary →warnings[]: yarn_pnp_unsupportedin all three modes (hosted keeps its empty redirect block) andapply --json→error.code: yarn_pnp_unsupported, exit 1; no files touched, no.socket/created.🤖 Generated with Claude Code
Note
Medium Risk
Behavior change for PnP projects: install-hook
applywithout a manifest now exits 1 instead of silent 0, and scan contract adds machine-readable warnings while keeping success exit codes—intentional but user-visible for CI and hooks.Overview
Fixes a P0 where yarn and pnpm Plug'n'Play projects looked healthy while npm deps were never discovered: empty crawler + lockfile inventory that swallowed PnP diagnosis produced
scanexit 0 /scannedPackages: 0with no warnings, andapplyfell through to calmnoManifestexit 0.Core —
inventory_npm_locknow returnsResultand propagatesUnsupportedNpmLayoutfor PnP loaders instead of treating them like “no lockfile.”inventory_project_diagnosedexposes those refusals alongside other ecosystems’ lock entries;inventory_projectunchanged for vendor/repair callers.scan — Lockfile supplement collects layout refusals and surfaces them as additive top-level
warnings[]({code, detail}) on JSON envelopes (empty and polyglot non-empty paths) plusWarning (<code>): …on stderr. Codes align with apply:yarn_pnp_unsupported,pnpm_pnp_unsupported. Exit code andstatusstay exit 0 /success(same posture as hosted refusals).apply — Yarn berry PnP detection runs before the missing-manifest early return via shared
refuse_yarn_pnp, so PnP checkouts get exit 1 /yarn_pnp_unsupportedeven when scan never created.socket/.CLI_CONTRACT.mddocuments the scan warning channel and the noManifest carve-out. E2e coverage ine2e_safety_yarn_pnp.rspins all three scan modes, human stderr, pnpm twin, and no-manifest apply.Reviewed by Cursor Bugbot for commit cd894fe. Configure here.