Repository navigation
Fix Go replaces that go.work ignores (#393, #458, #531) - #1393
Merged
Mikola Lysenko (mikolalysenko) merged 6 commits intoOct 11, 2026
Merged
Conversation
This was referenced Oct 11, 2026
In Go workspace mode a project go.mod replace does nothing when the go.work in effect replaces the same module itself (#393) or doesn't `use` the project (#458), and it breaks every workspace build when another member replaces the same module@version elsewhere (#531). socket-patch wrote the replace anyway and its checks called it in sync, so the build stayed unpatched (or unbuildable) while apply, vendor and their --check passes all reported success. A new go_consumer_sync::workspace_conflict finds the go.work go would use (GOWORK, else the nearest one above the project) and says which of the three cases applies. ensure_replace_entry, which writes the replace for agent apply and vendor, now refuses such a replace and names the fix. audit reports one already written as go_workspace_replace_ineffective, so apply --check and vendor --check fail and apply, vendor and hosted scan warn. Assisted-by: Claude Code:claude-opus-5-5
vex attested not_affected for a Go patch whose go.mod replace the go.work in effect overrides, ignores or makes conflict, although the build links the user's target, the unpatched module, or nothing at all. It now omits such a patch with go_workspace_override and warns with go_workspace_replace_ineffective. Adds real-go regression tests for #393, #458 and #531: apply refuses, and a replace written before the go.work appeared fails apply --check and is left out of VEX. Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
Review follow-ups on the go.work check: - apply and vendor now check before copying or fetching anything, so apply --dry-run predicts the refusal, a re-run over an inert replace fails instead of reporting it in sync, and a refused apply leaves no orphan copy under .socket/go-patches. vendor refuses with its own vendor_go_workspace_conflict code. - A go.work replace counts as ours only when it sends this version to the project's own copy, however the path is spelled; when it does, other members' replaces are ignored, as go ignores them. - Replacement targets are compared lexically, as go compares them, and GOWORK set with `go env -w` is honoured. - The workspace is read once per audit and shared with the modules.txt check. Assisted-by: Claude Code:claude-opus-5-5
Mikola Lysenko (mikolalysenko)
marked this pull request as ready for review
October 11, 2026 17:41
Collaborator
Author
|
BugBot review Generated by Claude Code |
The go.work check read GOWORK with its own env-file reader, which skipped the `go env -w` file when GOWORK was set but empty and opened the file without the regular-file guard. Use utils::go_env, which resolves settings exactly as go does and reads the file safely. Assisted-by: Claude Code:claude-opus-5-5
Collaborator
Author
|
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 e91342d. Configure here.
Collaborator
Author
|
Ready for review at
Worth a look: Go replace writers and checks now consult the go.work in effect (including Generated by Claude Code |
Tanmay Singla (Tanmay182003)
approved these changes
Oct 11, 2026
Mikola Lysenko (mikolalysenko)
deleted the
agent/fix-go-work-blind-redirect
branch
October 11, 2026 19:56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
LLM Description written by Claude Code:claude-opus-5-5
Fixes #393
Fixes #458
Fixes #531
Summary
In a Go workspace,
applyandvendorno longer write a go.modreplacethat the
go.workin effect would override, ignore or reject.apply --checkand
vendor --checknow fail on such a replace that was written earlier, andvexno longer attests the patch asnot_affected.Shared root cause
Every Go mode wires a patch by writing a
replaceinto the project's owngo.mod. Every check (apply --check,vendor --check, VEX) then treatedthat line as effective, and none of them asked which
go.workthe go commanduses. In workspace mode Go:
go.workreplace of the module, for any version, override everymember's replace (A user replace in go.work silently overrides the Socket go.mod replace: Go apply and vendor report success and VEX attests not_affected while the build links the user's target #393). The build links the user's target;
go.workdoesn'tuse(Go apply writes its replace into a root go.mod that go.work doesn'tuse, so the workspace build links the unpatched module while apply, apply --check and VEX all report it patched #458). Thebuild stays unpatched;
module@versionwith differenttargets (Running Go apply in a second go.work member writes a second replace for the same module@version, so every workspace build fails with "conflicting replacements" while apply, apply --check and VEX all report success #531). The whole workspace stops building.
In all three cases socket-patch exited 0,
--checksaid "in sync" and VEXattested
not_affected.Fix (one shared boundary)
vendor::go_consumer_syncalready knew how to find the workspace for itsvendor/modules.txtaudit. It now:go.workin effect once:GOWORKfrom the environment or fromgo env -w, otherwise the nearestgo.workat or above the project. Itreads go.work's replaces, its
usemembers and each member's replaces;workspace_conflict/replace_refusal, which decide which of thethree cases applies to a
module@versionreplace, matching go's own rules:go.work's own replace for this version sends it to the project's copy;
never conflicts.
Callers:
apply(golang_local::apply_go_redirect) andvendor(
vendor::golang::vendor_go_module, new codevendor_go_workspace_conflict)refuse before copying or fetching anything. Dry runs and re-runs over an
inert replace therefore refuse too, and no orphan copy is left.
go_mod_edit::ensure_replace_entryrefuses as well, as a second guard.auditreports a replace that was already written asgo_workspace_replace_ineffective. As a resultapply --checkandvendor --checkfail, andapply,vendorandscan --mode hostedwarn.vexleaves the patch out with the new omission taggo_workspace_override, plus the warning.Docs:
docs/ecosystems.md(new "Go workspaces (go.work)" section) and thegolang entry in
CLI_CONTRACT.md. No wrapper changes: the npm, pypi and gemwrappers only dispatch to the binary.
Per-issue tests (red on main → green)
The three e2e tests below use the real
gotoolchain with a hermetic fileGOPROXY. On
main(fix stashed) all three fail at the first assertion: applyexits 0. With the fix they pass.
e2e_golang_build::go_work_replace_overrides_the_socket_replace.A go.work replace to
../forkmakesapply --dry-run,applyand a re-runrefuse. go.mod is untouched, no copy is left, and the build still links
FORK. For a replace written before the go.work:
apply --checkexits 1 andvexomits it withgo_workspace_override. Unit test:go_consumer_sync::go_work_replace_of_the_module_makes_the_replace_inert(versioned, version-less, other-version and module replaces; ours moved
into go.work is fine).
use, so the workspace build links the unpatched module while apply, apply --check and VEX all report it patched #458:e2e_golang_build::go_work_without_the_project_ignores_its_replace.With
go.work: use ./svc, apply refuses. A replace written earlier fails--checkand is omitted from VEX. Positive control: afteruse (. ./svc)the svc build prints PATCHED and
--checkpasses. Unit test:go_work_that_does_not_use_the_project_makes_the_replace_inert(ancestorgo.work, and every spelling of the member path).
e2e_golang_build::go_work_member_replaces_must_not_conflict.Applying in member
apatches the workspace. Applying inbrefuses, andthe workspace still builds PATCHED. Replaces written in both members before
the go.work existed reproduce "conflicting replacements", fail
--checkinboth members and are omitted from VEX. Unit test:
member_replaces_of_one_module_version_must_agree(same target, otherversion and hosted replaces don't conflict).
go_work_replace_is_judged_by_where_it_points(from review) andgo_mod_edit::test_ensure_refuses_a_replace_go_work_defeats(all threecases, dry run included, go.mod unchanged).
Commands run
cargo fmt --all -- --check: okcargo clippy --workspace --all-features -- -D warnings: okcargo test -p socket-patch-core --all-features: 6276 passed. The 4 that failed are read-only-permission tests that can't fail as root in this sandbox (copy_tree, vlt_heal, pypi_poetry, pypi_requirements); they're unrelated to Go. A fullcargo test --workspace --all-featuresbuild ran out of the sandbox's disk allowance, so CI runs the restcargo test -p socket-patch-cli --all-features --lib --bins: 911 passedcargo test -p socket-patch-cli --all-features --test e2e_vex_lockfile --test covgap_commands_vex --test e2e_vex_vendor --test e2e_vex_redirect --test covgap_commands_rollback: all passSOCKET_PATCH_GO_E2E_REQUIRED=1 cargo test -p socket-patch-cli --all-features --test e2e_golang_build --test e2e_golang_workspace_build --test e2e_vendor_golang_build --test e2e_golang_hosted_build --test e2e_golang: all pass (go 1.24.7)/code-review highfindingsFixed in
1413d3c:socket_owned; it now compares theactual target.
copies.
vendorre-runs used a misleading error code.go env -w GOWORKwas ignored. Bugbot then pointed out that my own env-file reader mishandled an empty GOWORK and skipped the regular-file guard;e91342dswitches to the sharedutils::go_env.workspace_replaces.Not fixed: the hosted writer doesn't refuse.
scan --mode hostedrewritesthrough the pure, in-memory
rewrite_golang, which only sees project-relativefile contents and can't look at an ancestor or
GOWORKgo.work. Hosted modeis covered by the post-write warning, the
--checkfailure and the VEXomission, so it never makes a false attestation. A write-time refusal there
needs the go.work context passed into the rewriter. That's left as a
follow-up rather than widening this PR. For #531 hosted mode was never
affected: identical module replaces don't conflict.
🤖 Generated with Claude Code
https://claude.ai/code/session_018LqNhpS1viyHzW6Jmp3Wuq
Generated by Claude Code