[agent] Found by the scheduled npm bug-hunt routine (ledger #302).
Summary
An npm project has left-pad@1.3.0 vendored at patch A. The API then publishes a superseding free patch B for the same name@version, but B's prebuilt artifact isn't available yet: /patches/package answers pending_build, or build_failed / not_found. From then on, every socket-patch scan --mode vendored prints Error: Failed to vendor pkg:npm/left-pad@1.3.0: prebuilt artifact is still building and Vendored 0 packages; 1 failed., with status: "partial_failure" and exit 1.
Nothing is actually wrong with the project. The ledger, the lock and .socket/vendor/npm/<A>/ are untouched, vendor --check passes, a cold npm ci installs A's patched bytes, and vex attests not_affected. The run reports a failure for a package that is still patched.
Hosted mode handles the same upgrade calmly. It keeps pin A, lists B in redirect.skipped[] with reason: "pending_build" (or build_failed / not_found), and exits 0.
Impact
- A scheduled CI job that runs
scan --mode vendored (to pick up new patches) turns red as soon as any already-vendored package gets a newer patch whose artifact isn't built yet. With build_failed or not_found it stays red until the server changes, and no local remedy exists: get <B> --mode vendored fails the same way. The bare vendor command doesn't help either, since it only reads the manifest or ledger.
- The human message, "Failed to vendor pkg:npm/left-pad@1.3.0", suggests the package isn't vendored or patched, but it still is, at A.
- The behaviour depends on the mode: hosted exits 0 on the identical API state.
Repro (Linux, main 9c43dfc)
A local mock of the org-scoped API serves left-pad@1.3.0 with patch A (1111…) granted, and patch B (2222…, newer publishedAt, also free) whose reference answers pending_build. sp is socket-patch … --api-url http://127.0.0.1:8787 --org o --api-token fake --patch-server-url http://127.0.0.1:8787.
mkdir app && cd app
echo '{"name":"app","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}' > package.json
npm install
# API offers only A (granted)
sp scan --mode vendored --yes # Vendored 1 package. exit 0
# API now offers A and B; /patches/package answers {"status":"pending_build"} for B
sp scan --mode vendored --yes
# 1 package has a newer patch available.
# Error: Failed to vendor pkg:npm/left-pad@1.3.0: prebuilt artifact is still building
# Vendored 0 packages; 1 failed. exit 1 (same on every re-run)
sp vendor --check # committed artifact and wiring verified, exit 0
rm -rf node_modules && npm ci && head -1 node_modules/left-pad/index.js # // SOCKET-PATCHED-A
sp vex -O v.json # "status": "not_affected"
--json: status: "partial_failure", vendor.summary.failed: 1, one apply_failed event, and updates[] lists A→B.
The hosted control on the same mock state (sp scan after a hosted scan to A) prints Switched 0 packages to hosted patches … No patches could be switched to hosted:, has redirect.skipped: [{uuid: B, reason: "pending_build"}], keeps pin A, and exits 0.
With build_failed and not_found for B, the vendored run fails the same way (prebuilt artifact unavailable: not_found, exit 1). The result is the same whether the API still offers A alongside B or offers only B.
Controls that pass: B paid with forbidden (no paid access) and A withdrawn (nothing offered) both keep A, exit 0, in both modes.
Expected vs actual
- Expected: CLI_CONTRACT.md's rollout table classes this row as UPGRADE ("the selection supersedes the recorded uuid … the writer gets the selected uuid"). When the writer can't get B, hosted keeps the recorded uuid and reports the skip without failing. Vendored should do the same: keep A and warn (e.g.
vendor_prebuilt_pending as a skipped event), and not count the package as failed, the way hosted mode treats the identical reference statuses (describe_skip_reason: "the hosted artifact is still being built; re-run later").
- Actual:
partial_failure, exit 1 on every run until the server builds B, and a message that implies the package isn't vendored.
OS × version
| OS |
npm |
lockfileVersion |
vendored re-scan |
hosted re-scan |
| Linux |
8.19.4 (Node 22.22) |
2 |
exit 1 (x2) |
exit 0, keeps A |
| Linux |
10.9.4 (Node 22.22) |
3 |
exit 1 (x2, plus build_failed / not_found variants) |
exit 0, keeps A |
| Linux |
12.2.0 (Node 24.21) |
3 |
exit 1 (x2) |
exit 0, keeps A |
macOS and Windows weren't probed: this is the OS-independent vendor service-fetch path. It probably isn't npm-specific (ServiceFetch::settle is shared by the vendor backends), but I only verified npm. v4.0.0 isn't comparable: it fell back to a local build (--vendor-source auto), which v5 removed, so I didn't bisect.
Suspect code
crates/socket-patch-core/src/vendor/service_fetch.rs:228-236 (miss): discards the vendor_prebuilt_pending / vendor_prebuilt_unavailable warning (let _ = (warnings, code)) and turns the outcome into a hard vendor_prebuilt_required failure. With ServiceTerminal::Failure that becomes done_failure (:257-271 for Pending / Unavailable).
crates/socket-patch-cli/src/commands/scan/vendor_flow.rs: the UPGRADE row is handed to the vendor backend at B without a fallback to the recorded uuid when B's reference isn't granted. Hosted checks the reference status first (crates/socket-patch-cli/src/commands/scan/hosted.rs:2314-2325) and skips.
No probe runs (probe branches are on hold for this routine; see ledger #302).
Backlog review — 2026-10-08
Priority: P1 → P2. A pending or failed newer artifact makes re-scan fail while the previous vendored patch remains intact.
[agent] Found by the scheduled npm bug-hunt routine (ledger #302).
Summary
An npm project has
left-pad@1.3.0vendored at patch A. The API then publishes a superseding free patch B for the samename@version, but B's prebuilt artifact isn't available yet:/patches/packageanswerspending_build, orbuild_failed/not_found. From then on, everysocket-patch scan --mode vendoredprintsError: Failed to vendor pkg:npm/left-pad@1.3.0: prebuilt artifact is still buildingandVendored 0 packages; 1 failed., withstatus: "partial_failure"and exit 1.Nothing is actually wrong with the project. The ledger, the lock and
.socket/vendor/npm/<A>/are untouched,vendor --checkpasses, a coldnpm ciinstalls A's patched bytes, andvexattestsnot_affected. The run reports a failure for a package that is still patched.Hosted mode handles the same upgrade calmly. It keeps pin A, lists B in
redirect.skipped[]withreason: "pending_build"(orbuild_failed/not_found), and exits 0.Impact
scan --mode vendored(to pick up new patches) turns red as soon as any already-vendored package gets a newer patch whose artifact isn't built yet. Withbuild_failedornot_foundit stays red until the server changes, and no local remedy exists:get <B> --mode vendoredfails the same way. The barevendorcommand doesn't help either, since it only reads the manifest or ledger.Repro (Linux, main
9c43dfc)A local mock of the org-scoped API serves
left-pad@1.3.0with patch A (1111…) granted, and patch B (2222…, newerpublishedAt, also free) whose reference answerspending_build.spissocket-patch … --api-url http://127.0.0.1:8787 --org o --api-token fake --patch-server-url http://127.0.0.1:8787.--json:status: "partial_failure",vendor.summary.failed: 1, oneapply_failedevent, andupdates[]lists A→B.The hosted control on the same mock state (
sp scanafter a hosted scan to A) printsSwitched 0 packages to hosted patches … No patches could be switched to hosted:, hasredirect.skipped: [{uuid: B, reason: "pending_build"}], keeps pin A, and exits 0.With
build_failedandnot_foundfor B, the vendored run fails the same way (prebuilt artifact unavailable: not_found, exit 1). The result is the same whether the API still offers A alongside B or offers only B.Controls that pass: B paid with
forbidden(no paid access) and A withdrawn (nothing offered) both keep A, exit 0, in both modes.Expected vs actual
vendor_prebuilt_pendingas askippedevent), and not count the package as failed, the way hosted mode treats the identical reference statuses (describe_skip_reason: "the hosted artifact is still being built; re-run later").partial_failure, exit 1 on every run until the server builds B, and a message that implies the package isn't vendored.OS × version
build_failed/not_foundvariants)macOS and Windows weren't probed: this is the OS-independent vendor service-fetch path. It probably isn't npm-specific (
ServiceFetch::settleis shared by the vendor backends), but I only verified npm. v4.0.0 isn't comparable: it fell back to a local build (--vendor-source auto), which v5 removed, so I didn't bisect.Suspect code
crates/socket-patch-core/src/vendor/service_fetch.rs:228-236(miss): discards thevendor_prebuilt_pending/vendor_prebuilt_unavailablewarning (let _ = (warnings, code)) and turns the outcome into a hardvendor_prebuilt_requiredfailure. WithServiceTerminal::Failurethat becomesdone_failure(:257-271forPending/Unavailable).crates/socket-patch-cli/src/commands/scan/vendor_flow.rs: the UPGRADE row is handed to the vendor backend at B without a fallback to the recorded uuid when B's reference isn't granted. Hosted checks the reference status first (crates/socket-patch-cli/src/commands/scan/hosted.rs:2314-2325) and skips.No probe runs (probe branches are on hold for this routine; see ledger #302).
Backlog review — 2026-10-08
Priority: P1 → P2. A pending or failed newer artifact makes re-scan fail while the previous vendored patch remains intact.