[agent] Found by the scheduled Go modules bug-hunt routine (ledger #317).
Summary
In a Go project with a committed vendor/ directory (go mod vendor), both agent-mode apply and vendored-mode vendor add a go.mod replace and exit 0. Neither touches vendor/modules.txt. From then on, every go build / go run / go test fails:
go: inconsistent vendoring in …/c:
example.com/upstream@v1.0.0: is replaced in go.mod, but not marked as replaced in vendor/modules.txt
With a vendor/ directory and go ≥ 1.14 in go.mod, go defaults to -mod=vendor and checks that modules.txt records every go.mod replace. apply --check still says Patch redirects are in sync (exit 0). vex attests not_affected. Nothing tells the user to re-run go mod vendor. rollback then breaks the build a second time, the other way round ("is marked as replaced in vendor/modules.txt, but not replaced in go.mod").
Impact
Committing vendor/ is common in large Go repos (Kubernetes-style monorepos, air-gapped CI). There, a successful socket-patch apply or vendor leaves the default build broken on every machine and in CI. That's "a rewrite that makes the next frozen install fail", and the CLI's own audit (apply --check) and VEX both call the state healthy. Running go mod vendor afterwards does produce a correct patched build (it copies the patched tree into vendor/), so the fix is to either do that sync (or its modules.txt equivalent) or refuse or warn up front.
Repro (hermetic file GOPROXY, the same fixture shape as tests/e2e_golang_build.rs)
export GOTOOLCHAIN=local GOSUMDB=off GOENV=off GOFLAGS= GOPROXY=file://$T/proxy GOMODCACHE=$T/modcache
# consumer requires example.com/upstream v1.0.0; go.mod says `go 1.16`
go mod vendor && go build ./... # ok
socket-patch apply --offline --ecosystems golang # "1 of 1 targeted patch applied", exit 0
go build ./... # exit 1: inconsistent vendoring (above)
socket-patch apply --check --ecosystems golang # "Patch redirects are in sync (1 redirect checked)", exit 0
socket-patch vex --offline --output vex.json # exit 0, not_affected
go mod vendor && go run . # OUT: PATCHED (the manual sync fixes it)
socket-patch rollback --offline --ecosystems golang # exit 0
go build ./... # exit 1: "is marked as replaced in vendor/modules.txt, but not replaced in go.mod"
socket-patch vendor --offline --ecosystems golang in place of apply behaves the same: exit 0, the replace => ./.socket/vendor/golang/<uuid>/… is written, the build fails with the same error, and vex attests not_affected.
Expected vs actual
- Expected:
docs/ecosystems.md ("Go: directory replaces and go.sum") describes the replace as the complete mechanism ("the committed patched tree itself is the protection … the wiring survives go mod tidy"). docs/design/golang-hosted.md says "go mod vendor vendors the PATCHED bytes … the vendored escape hatch composes". So a project that uses vendor/ should keep building after apply / vendor: either vendor/modules.txt (and vendor/<module>) get synced, or the command refuses or at least warns (go mod vendor required) and apply --check reports the inconsistency.
- Actual: exit 0, a broken build,
apply --check in sync, and VEX attests. vendor/modules.txt is never read or written anywhere in crates/ (README: "vendor/modules.txt is not read").
| OS |
go |
agent apply |
vendored vendor |
| Linux |
1.16.15 |
fail (build broken, exit 0) |
fail |
| Linux |
1.21.13 |
fail |
fail |
| Linux |
1.24.7 (local, 5 repros) |
fail |
fail |
| Linux |
1.26.3 |
fail |
fail |
| macOS |
1.24.13 / 1.26.3 |
fail |
fail |
| Windows |
1.16.15 / 1.21.13 / 1.26.3 |
not reached: Go apply/vendor already fail on Windows with Access is denied. (os error 5), to be filed separately |
not reached |
Release 4.0.0 behaves the same (checked locally). I couldn't compare 3.3.0 because it doesn't accept the hand-staged manifest. A go 1.13 go.mod with an explicit -mod=vendor fails the same way on go 1.24.
Suspect code
crates/socket-patch-core/src/patch/redirect/golang_local.rs:170 apply_go_redirect → go_mod_edit::ensure_replace_entry (crates/socket-patch-core/src/vendor/go_mod_edit.rs:175): only go.mod is edited.
crates/socket-patch-core/src/vendor/golang.rs:209 vendor_go_module: same.
golang_local::verify_go_redirect_state (apply --check) doesn't look at vendor/modules.txt.
[agent] Found by the scheduled Go modules bug-hunt routine (ledger #317).
Summary
In a Go project with a committed
vendor/directory (go mod vendor), both agent-modeapplyand vendored-modevendoradd ago.modreplaceand exit 0. Neither touchesvendor/modules.txt. From then on, everygo build/go run/go testfails:With a
vendor/directory andgo ≥ 1.14ingo.mod, go defaults to-mod=vendorand checks thatmodules.txtrecords everygo.modreplace.apply --checkstill saysPatch redirects are in sync(exit 0).vexattestsnot_affected. Nothing tells the user to re-rungo mod vendor.rollbackthen breaks the build a second time, the other way round ("is marked as replaced in vendor/modules.txt, but not replaced in go.mod").Impact
Committing
vendor/is common in large Go repos (Kubernetes-style monorepos, air-gapped CI). There, a successfulsocket-patch applyorvendorleaves the default build broken on every machine and in CI. That's "a rewrite that makes the next frozen install fail", and the CLI's own audit (apply --check) and VEX both call the state healthy. Runninggo mod vendorafterwards does produce a correct patched build (it copies the patched tree intovendor/), so the fix is to either do that sync (or itsmodules.txtequivalent) or refuse or warn up front.Repro (hermetic file GOPROXY, the same fixture shape as
tests/e2e_golang_build.rs)socket-patch vendor --offline --ecosystems golangin place ofapplybehaves the same: exit 0, thereplace => ./.socket/vendor/golang/<uuid>/…is written, the build fails with the same error, andvexattestsnot_affected.Expected vs actual
docs/ecosystems.md("Go: directory replaces and go.sum") describes thereplaceas the complete mechanism ("the committed patched tree itself is the protection … the wiring survivesgo mod tidy").docs/design/golang-hosted.mdsays "go mod vendorvendors the PATCHED bytes … the vendored escape hatch composes". So a project that usesvendor/should keep building afterapply/vendor: eithervendor/modules.txt(andvendor/<module>) get synced, or the command refuses or at least warns (go mod vendorrequired) andapply --checkreports the inconsistency.apply --checkin sync, and VEX attests.vendor/modules.txtis never read or written anywhere incrates/(README: "vendor/modules.txtis not read").Matrix (probe run https://github.com/SocketDev/socket-patch/actions/runs/36746894687, plus local)
applyvendorAccess is denied. (os error 5), to be filed separatelyRelease 4.0.0 behaves the same (checked locally). I couldn't compare 3.3.0 because it doesn't accept the hand-staged manifest. A
go 1.13go.modwith an explicit-mod=vendorfails the same way on go 1.24.Suspect code
crates/socket-patch-core/src/patch/redirect/golang_local.rs:170apply_go_redirect→go_mod_edit::ensure_replace_entry(crates/socket-patch-core/src/vendor/go_mod_edit.rs:175): onlygo.modis edited.crates/socket-patch-core/src/vendor/golang.rs:209vendor_go_module: same.golang_local::verify_go_redirect_state(apply --check) doesn't look atvendor/modules.txt.