Fix apply failing when patched deps are skipped (#403) - #555
Conversation
Assisted-by: Claude Code:claude-opus-5-5
`socket-patch apply` exited 1 when every manifest patch targeted a package the project's lockfile resolves but the package manager deliberately left uninstalled on this host: a platform-gated optional dependency like fsevents or @esbuild/<os>-<cpu>, or a devDependency after `npm ci --omit=dev`. CI on other platforms and production deploys failed although the tree was already correct. Such purls are now a calm package_not_installed skip with a lockfile-only detail and a human note, matching scan --apply. An unmatched purl that no lock resolves still fails the all-miss run, so the wrong --cwd guard is kept. Fixes #403 Assisted-by: Claude Code:claude-opus-5-5
325d73c to
2b8466b
Compare
|
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 2b8466b. Configure here.
|
[burn-down agent] Labeled Ready for review at
Generated by Claude Code |
|
Reviewed No actionable correctness/security regressions found. Checked lockfile matching, scoped/qualified PURLs, global-scope exclusion, mixed unresolved/lock-resolved misses, and preservation of actual patch/variant failures. Validation: |
|
Second review pass of The head is unchanged from the prior review, discussions introduce no unresolved finding, and it merges cleanly with current main |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #403
Summary
socket-patch applyno longer exits 1 when the only manifest patches are for packages that the project's lockfile resolves but the package manager deliberately left uninstalled on this host. Examples are a platform-gated optional dependency (fseventson Linux,@esbuild/<os>-<cpu>) and a devDependency afternpm ci --omit=dev. Before this change, CI on "the other" platform and production deploys runningnpm ci --omit=dev && socket-patch applyfailed even though the tree was already correct.Root cause
Agent-mode
apply(crates/socket-patch-cli/src/commands/apply.rs) failed the run when no targeted manifest patch matched an installed package. That happened both on the empty-crawl path (success: unmatched.is_empty()) and on thenone_matchedpath. Neither path looked at the project's lockfiles. If any other patch matched, the same entry was only a warning (exit 0).scan --applyalready treats lockfile-only packages as a calmskipped/package_not_installed, as CLI_CONTRACT.md says.Fix
lockfile_resolved()reads the project lock inventory (ProjectContext::locks, the same inventory behindscan's lockfile supplement) and marks the unmatched purls the lockfiles resolve. It uses thenormalize_purl/qualifier-stripped comparison and Composer'spurl_eq, mirroringlockfile_only_contains. Global runs have no project lock, so nothing is lockfile-resolved there.skipped/package_not_installedevent, with the detail "Resolved by the project lockfile but not installed on this host (lockfile-only)".Note:block instead of theError:block.--silent/--jsonmute it.--cwdguard is kept: no lock means behavior is unchanged.package_not_installedrow, rollback asymmetry note) and the doc comment on the pinnedunmatched_purl_exit_semantics_are_pinnedinvariant are updated. That invariant's ghost purl has no lock, so both of its halves are unchanged.apply_patches_inner's poll state: the lock read isBox::pinned, per the Windows 1 MiB stack note.npm/,pypi/,gem/) only dispatch to the binary, so they need no change.Test evidence
New suite
crates/socket-patch-cli/tests/apply/lockfile_only_skip.rs(cargo test -p socket-patch-cli --all-features --test apply lockfile_only_skip):apply.rs): 4 failed, 1 passed. Theno_lockfile_all_miss_still_failscontrol passes on both.Per-issue checklist (#403):
fsevents,os: ["darwin"]): exit 0,success, calm skip event, human note and noError:. Covered byplatform_skipped_optional_dependency_is_a_calm_skip.apply --silent: exit 0 and silent. Covered bysilent_apply_on_platform_skipped_optional_dependency_exits_zero.npm ci --omit=devdevDependency (dev: true): exit 0. Covered byomitted_dev_dependency_is_a_calm_skip.unresolved_purl_still_fails_alongside_a_lock_resolved_one.no_lockfile_all_miss_still_fails.[email protected], so[email protected]is in the lock but not installed on Linux, with a manifest holding onlypkg:npm/[email protected]).apply --offlineexits 0 with the note, andapply --offline --silentexits 0.Local checks:
cargo clippy --workspace --all-features -- -D warnings: clean.cargo fmt --all -- --check: main itself isn't rustfmt-clean (acargo fmt --allrewrites 129 files on61cfb9b), and CI doesn't run fmt. I checked that this PR's hunks and the new file are rustfmt-clean, and the onlyrustfmt --checkdiff left inapply.rsis a pre-existing one that main also has.cargo test --workspace --all-features --no-fail-fast: 9473 passed, 12 failed, 246 ignored. All 12 failures are permission-based fixtures (chmodread-only dirs or unremovable files, e.g.covgap_commands_vendor::*_state_write_failure_*,vendor::pypi_poetry::tests::wire_write_failure_*,patch::copy_tree::tests::relax_loop_must_not_traverse_symlinked_root). They can't fail as root (the sandbox runs as uid 0), and none of them touchapply. CI runs as non-root.node --test npm/socket-patch/bin/socket-patch.test.mjs: 4/4.CI on
2b8466b: all 407 check runs finished, 401 success and 6 skipped. Bugbot reviewed2b8466band found no issues.Note
Medium Risk
Changes documented exit-code and failure semantics for
apply, which CI and postinstall hooks may gate on, though behavior is narrowed to lockfile-evidenced misses only.Overview
socket-patch applyno longer exits 1 when manifest patches only target packages the lockfile resolves but that are intentionally absent on disk (e.g. platform optionalfsevents, devDependencies afternpm ci --omit=dev). That aligns agentapplywithscan --apply, which already treated those as benign skips.The command now cross-checks unmatched manifest purls against
ProjectContextlock inventory and splits them into lockfile-resolved vs truly unresolved. Only unresolved purls can triggerpartialFailure, stderrError:lines, or the all-miss failure path; lockfile-only entries still emitskipped/package_not_installedwith an explicit lockfile-only detail and an optional humanNote:(muted under--json/--silent).CLI_CONTRACT.mdand integration tests inlockfile_only_skip.rspin the behavior; wrong---cwd/ no-lock cases stay exit 1.Reviewed by Cursor Bugbot for commit 2b8466b. Configure here.
Generated by Claude Code