Skip to content

Agent-mode Go rollback and remove after go mod tidy drop the replace but not restore the module's go.sum lines, so the next default go build fails with "missing go.sum entry" while rollback exits 0 with no warning #549

Description

[agent] Found by the scheduled Go modules bug-hunt routine (ledger #317).

Summary

In agent mode, apply points the patched module at .socket/go-patches/<module>@<version>/ with a directory replace. Go doesn't need go.sum lines for a directory replace, so go mod tidy removes the original module's h1: lines from go.sum. docs/ecosystems.md says "the wiring survives go mod tidy", so running tidy after apply is a supported workflow.

rollback (and remove <purl>) then drops the replace and the copy but leaves go.sum as tidy left it. The result is a go.mod that requires the module with no go.sum entry. The next go build with default flags (-mod=readonly) fails with missing go.sum entry for module providing package …. Rollback still reports status: success with warnings: [].

Impact

After a routine socket-patch rollback or remove, every CI build and every fresh go build/go test is red until someone runs go mod tidy or go get by hand. Nothing in the output says this is needed. Other paths already handle this:

  • The hosted leg re-resolves and restores the original module's go.sum lines.
  • The hosted→vendored takeover warns that go mod tidy will be needed after vendor --revert (vendor/golang.rs ~L364).

The agent leg does neither.

Repro (hermetic, Linux, go 1.24.7)

SP=/path/to/target/release/socket-patch
W=$(mktemp -d); M=example.com/upstream; V=v1.0.0
mkdir -p $W/stage/$M@$V $W/proxy/$M/@v $W/c/.socket/blobs
printf 'module %s\n\ngo 1.21\n' $M > $W/stage/$M@$V/go.mod
printf 'package upstream\n\nfunc Greeting() string { return "PRISTINE" }\n' > $W/stage/$M@$V/lib.go
printf 'package upstream\n\nfunc Greeting() string { return "PATCHED" }\n' > $W/patched.go
echo "{\"Version\":\"$V\"}" > $W/proxy/$M/@v/$V.info; cp $W/stage/$M@$V/go.mod $W/proxy/$M/@v/$V.mod
(cd $W/stage && zip -qrD $W/proxy/$M/@v/$V.zip $M@$V)
export GOMODCACHE=$W/modcache GOPROXY=file://$W/proxy GOSUMDB=off GOTOOLCHAIN=local
cd $W/c
printf 'module example.com/consumer\n\ngo 1.21\n\nrequire %s %s\n' $M $V > go.mod
printf 'package main\n\nimport (\n\t"fmt"\n\t"%s"\n)\n\nfunc main() { fmt.Println("OUT:", upstream.Greeting()) }\n' $M > main.go
go mod download $M@$V && go mod tidy            # go.sum: 2 lines
gsha(){ python3 -c "import hashlib,sys;d=open(sys.argv[1],'rb').read();print(hashlib.sha256(b'blob %d\0'%len(d)+d).hexdigest())" "$1"; }
B=$(gsha $W/stage/$M@$V/lib.go); A=$(gsha $W/patched.go); cp $W/patched.go .socket/blobs/$A
cat > .socket/manifest.json <<J
{"patches":{"pkg:golang/$M@$V":{"uuid":"4d5e6f70-8192-4a1b-8c2d-0123456789ab","exportedAt":"t","files":{"lib.go":{"beforeHash":"$B","afterHash":"$A"}},"vulnerabilities":{},"description":"","license":"","tier":""}},"setup":{"manual":["golang"]}}
J
$SP apply                 # exit 0, replace written
go mod tidy               # supported per docs; go.sum is now empty
go build -o /dev/null .   # OK (PATCHED)
$SP rollback              # exit 0, "Rolled back packages: pkg:golang/example.com/upstream@v1.0.0"
go build -o /dev/null .   # exit 1: main.go:5:2: missing go.sum entry for module providing package example.com/upstream

$SP remove pkg:golang/example.com/upstream@v1.0.0 instead of rollback gives the same result. Without the go mod tidy step, both pass (go.sum keeps its 2 lines).

Expected vs actual

  • Expected: CLI_CONTRACT.md "Rollback command contract (v5.0)" says a bare rollback "restores the SYSTEM to unpatched". docs/ecosystems.md "Go: directory replaces and go.sum" says the wiring "survives go mod tidy". So after apply → tidy → rollback, the project should build exactly as it did before apply. That means either restoring the module's h1: and /go.mod h1: lines (as the hosted leg does) or, at minimum, a warning plus a non-silent outcome telling the user to run go mod tidy.
  • Actual: go.mod's require is left with no go.sum lines. Rollback/remove exit 0 with warnings: [] (--json), and the next default go build fails.

Matrix (Linux; each cell run 2× on go 1.24.7, 1× on the others)

go apply → rollback (no tidy) apply → tidy → rollback apply → tidy → remove
1.22.12 pass fail fail
1.24.7 pass fail fail
1.26.8 pass fail fail
macOS / Windows untested (probe branches currently blocked) untested untested

Vendored mode (vendor --revert after tidy) is untested here: it needs the mock patch service. The same drop-without-go.sum path looks likely.

Tested on main 61cfb9b (CLI 4.0.0). There have been no Go code changes since 4.0.0, so this isn't a regression in the 4.x line.

Suspect code

  • crates/socket-patch-core/src/patch/redirect/golang_local.rs:316 (remove_go_redirect) only calls go_mod_edit::drop_replace_entry (crates/socket-patch-core/src/vendor/go_mod_edit.rs:193) and removes the copy. go.sum is never reconciled and no warning is raised. Compare the hosted-takeover warning at crates/socket-patch-core/src/vendor/golang.rs:364-377.

Activity

  1. mikolalysenko commented on Oct 2, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triaged as priority:p2 (Go modules). Confirmed on main: the agent-mode remove_go_redirect (patch/redirect/golang_local.rs) only calls go_mod_edit::drop_replace_entry and deletes the copy. It never reconciles go.sum and never warns, unlike the hosted leg and the hosted→vendored takeover warning (vendor/golang.rs ~L364). No open or merged PR covers this, and it doesn't share a cause with the other open Go issues (#458 and #531 are about go.work replace placement, #509 is about the hosted graph gate).


    Generated by Claude Code

  2. mikolalysenko commented on Oct 2, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Still reproduces on main bf0e0d1 (agent mode, 2/2, go 1.24.7). Vendored mode is affected too.

    Same hermetic fixture as the issue body. A local mock patch service (SOCKET_VENDOR_URL) serves a granted module zip with sha512 integrity, the shape prebuilt_common uses:

    socket-patch vendor            # exit 0; replace => ./.socket/vendor/golang/<uuid>/example.com/upstream@v1.0.0
    go mod tidy                    # go.sum: 0 lines (directory replace needs none)
    go run .                       # OUT: PATCHED
    socket-patch vendor --revert --json
    # {"status":"success","events":[{"action":"removed",...}]}, no warnings[]
    go build -o /dev/null .        # exit 1: missing go.sum entry for module providing package example.com/upstream
    • Reproduced 2/2.
    • Control: without the go mod tidy step, vendor --revert leaves a buildable project.
    • The vendored ledger keeps verbatim pre-vendor originals of the wiring it edits, but go.sum isn't one of them. Tidy is what changes go.sum, and nothing reconciles it on revert.
    mode apply/vendor → tidy → rollback/revert
    agent (rollback, remove) fail (1.22.12 / 1.24.7 / 1.26.8)
    vendored (vendor --revert) fail (1.24.7, new)
    hosted pass (it restores the sum lines, as noted in the body)

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:triagedbugSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentpm:goGo modulespriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions