Repository navigation
npm apply exits 1 when every patch targets a platform-skipped optional dependency (fsevents, @esbuild/*), so the setup hook fails npm ci and npm install on other OSes #403
Description
[agent] Found by the scheduled npm bug-hunt routine (ledger #302).
Summary
socket-patch apply exits 1 when every manifest patch targets a package that npm skipped on purpose on this host, such as a platform-specific optionalDependencies entry like fsevents (macOS only) or @esbuild/<os>-<cpu>. The tree is already in its correct end state: the package is in the lock with optional: true and an os/cpu filter that excludes this host, so there's nothing to patch. But apply still prints Error: The targeted manifest patch matched no installed package and fails.
Because socket-patch setup wires apply --silent into postinstall and dependencies, this makes npm ci and npm install fail on every OS that doesn't install that package. A team that records a patch for fsevents on a Mac breaks its Linux CI. A team that patches @esbuild/linux-x64 in Linux CI breaks every macOS and Windows developer's npm install.
The result depends on what else is in the manifest. Add one patch for a package that is installed, and the same unmatched optional package downgrades to a warning with exit 0.
Impact
- The installs fail (
npm error command failed … socket-patch apply --silent --ecosystems npm, exit 1) on npm 8, 10, 11 and 12 as soon as the only patch in the manifest is for a platform-skipped optional dependency. - Platform-gated optional packages are where many native or binary CVEs live (
fsevents,@esbuild/*,@rollup/rollup-*,@swc/core-*,@next/swc-*), so a cross-platform team hits this on its first such patch. - The released 4.0.0 and 3.3.0 behave the same way, so this isn't a regression.
Repro (Linux, main f6b7fb9)
mkdir app && cd app
echo '{"name":"app","version":"1.0.0","private":true,"dependencies":{"chokidar":"3.6.0"}}' > package.json
npm install # fsevents@2.3.3 is in the lock (optional, os: darwin) but not installed
# record a patch for pkg:npm/fsevents@2.3.3, as `socket-patch get`/`scan` on a Mac would; here
# it's hand-staged: .socket/manifest.json with package/fsevents.js before/after hashes + blobs
socket-patch setup --yes # postinstall/dependencies: npx @socketsecurity/socket-patch apply --silent --ecosystems npm
rm -rf node_modules && npm ci
# npm error command sh -c socket-patch apply --silent --ecosystems npm
# npm error Error: The targeted manifest patch matched no installed package:
# npm error - pkg:npm/fsevents@2.3.3
# exit 1
socket-patch apply --offline --json # status partialFailure, events [skipped/package_not_installed], exit 1With a second patch for an installed package (e.g. is-glob) in the same manifest, apply --silent patches it and exits 0: the fsevents entry becomes a warning.
Expected vs actual
Expected: a manifest entry whose package the project lock resolves, but which npm deliberately didn't install on this host (an optional entry whose os/cpu/libc excludes it, or more generally any lockfile-only entry), is a calm skip (skipped / package_not_installed) that never fails the run. That's exactly what CLI_CONTRACT.md promises for the same situation in scan --apply: it "partitions lockfile-only patches out BEFORE download (calm skipped/package_not_installed records — never an error exit …)". rollback already treats a not-installed package as satisfying its end state (CLI_CONTRACT.md, rollback results). The install hook that setup wires should never fail an install whose tree is correct.
Actual: apply (the command the setup hook runs) exits 1 with an error whenever no targeted patch matched an installed package, regardless of whether the lock shows the package was skipped on purpose.
| OS | npm | only-other-platform patches: apply / npm ci with the setup hook |
host + other-platform patches: apply |
|---|---|---|---|
| Linux | 8.19.4 | exit 1 / exit 1 (and npm install exit 1) |
— |
| Linux | 10.9.7 | exit 1 / exit 1 (and npm install exit 1) |
— |
| Linux | 11.20.0 | exit 1 / exit 1 (and npm install exit 1) |
exit 0, warning only |
| Linux | 12.1.0 | exit 1 / exit 1 (and npm install exit 1) |
— |
| Linux (ubuntu-latest) | 10.9.7 (probe) | exit 1 / exit 1 | exit 0, host package patched |
| Linux (ubuntu-latest) | 12.1.0 (probe) | exit 1 / exit 1 | exit 0, host package patched |
| macOS (macos-latest, arm64) | 10.9.7 (probe) | exit 1 / exit 1 | exit 0, host package patched |
| macOS (macos-latest, arm64) | 12.1.0 (probe) | exit 1 / exit 1 | exit 0, host package patched |
| Windows (windows-latest) | 10.9.7 (probe) | exit 1 / exit 1 | exit 0, host package patched |
| Windows (windows-latest) | 12.1.0 (probe) | exit 1 / exit 1 | exit 0, host package patched |
The local Linux cells use fsevents@2.3.3 via chokidar@3.6.0, with the main build in the hook. The probe cells use esbuild@0.20.2 with patches for the two @esbuild/* platform packages the runner doesn't install, and the host's own package in column 3. The released 4.0.0 and 3.3.0 also exit 1 on the Linux fsevents repro.
Suspect code
crates/socket-patch-cli/src/commands/apply.rs:2176-2181:none_matched(no targeted purl matched an installed package) setshas_errors = true, without consulting the lockfile.crates/socket-patch-cli/src/commands/apply.rs:1785-1810: the same decision on the empty-tree path (success: unmatched.is_empty()).- For comparison,
scan --apply's lockfile-only partition (CLI_CONTRACT.md "Lockfile supplement (v3.4)") already knows these packages are lockfile-resolved, and a calm skip there doesn't change the exit code.
Probe run: https://github.com/SocketDev/socket-patch/actions/runs/36796693378 (all 6 jobs: RESULT only-other … apply_silent_exit=1 … npm_ci_exit=1, RESULT host-plus-other … apply_silent_exit=0 host_patched=1).
Activity
mikolalysenko commentedon Oct 1, 2026
[agent] Triaged:
priority:p1(npm). Not a duplicate, and I found no fix PR. Note: #277 (on main2463257) removedsetup, so the "setup hook failsnpm ci" part of the impact no longer applies as written. The core defect is still on main:applysets an error exit whennone_matched(crates/socket-patch-cli/src/commands/apply.rs:2166-2169) without checking whether the lock shows the package was skipped on purpose for this platform. Any user-wiredpostinstall: socket-patch applystill breaks the same way.Generated by Claude Code
mikolalysenko commentedon Oct 1, 2026
[agent] Re-triage on main
2463257(the v5 consolidation, #277): still reproduces, Linux, npm 10.9.7.v5 removes
setup, so the hook part of this issue no longer applies to new projects. The core defect remains. Take a manifest whose only entry ispkg:npm/fsevents@2.3.3, installed as an optional dependency, which npm skips on Linux. On that manifest,apply --offline --jsonreturnspartialFailureand exits 1.That matters more in v5.
docs/migrating-to-v5.mdtells agent-mode projects to "explicitly runsocket-patch applyafter dependency installs in CI", so a CI job on a platform that skips the optional package fails. Projects that kept their v4postinstallhooks (the migration guide says "Existing hooks may still callapply") still failnpm ci/npm installthe same way.Generated by Claude Code
mikolalysenko commentedon Oct 1, 2026
[agent] Re-triage on main
61cfb9b: still reproduces. Linux, npm 10.9.4.The project has
optionalDependencies: {"fsevents":"2.3.3"}, which npm skips on Linux; the lock records it.get <uuid> --mode agentaddspkg:npm/fsevents@2.3.3to the manifest and exits 0. With that as the only manifest entry,apply --offline --jsonreturnspartialFailure/ exit 1 with[('pkg:npm/fsevents@2.3.3', 'skipped', 'package_not_installed')](two runs, both the same). With a second, installed patch in the manifest it exits 0. So only the all-skipped case trips the error exit, atcrates/socket-patch-cli/src/commands/apply.rs:2173-2176(none_matched).Generated by Claude Code
mikolalysenko commentedon Oct 2, 2026
[agent] New trigger for the same root cause, from the npm bug-hunt routine (ledger #302): a production install (
npm ci --omit=dev). This is far more common than platform-skipped optional dependencies. Tested on main61cfb9b, npm 10.9.4, Linux.So a deploy pipeline that runs
npm ci --omit=dev && socket-patch applyfails as soon as every recorded patch is for a devDependency. When some patch is for a prod dependency, the same unmatched entry exits 0.vexis fine here: it omits the devDependency (package_not_found) and attests only the installed one.Generated by Claude Code
mikolalysenko commentedon Oct 2, 2026
[agent] Claiming this issue (single-issue cluster; root cause: agent
applyfails a run in which no targeted patch matched an installed package, and it never checks the project's lockfiles for packages that were resolved but deliberately not installed). Branch: agent/fix-apply-lockfile-only-skip. Claim-ID: 2026-10-02T12:22:17Z-8d79f6Generated by Claude Code
mikolalysenko commentedon Oct 2, 2026
[agent] Draft PR: #555
Generated by Claude Code
Skip lockfile-only patches in apply calmly
Skip lockfile-only patches in apply calmly