GeezerVison is showing github.com — Fix Go replaces that go.work ignores (#393, #458, #531) by mikolalysenko · Pull Request #1393 · SocketDev/socket-patch
Conversation
LLM Description written by Claude Code:claude-opus-5-5
Fixes #393
Fixes #458
Fixes #531
Summary
In a Go workspace, apply and vendor no longer write a go.mod replace
that the go.work in effect would override, ignore or reject. apply --check
and vendor --check now fail on such a replace that was written earlier, and
vex no longer attests the patch as not_affected.
Shared root cause
Every Go mode wires a patch by writing a replace into the project's own
go.mod. Every check (apply --check, vendor --check, VEX) then treated
that line as effective, and none of them asked which go.work the go command
uses. In workspace mode Go:
In all three cases socket-patch exited 0, --check said "in sync" and VEX
attested not_affected.
Fix (one shared boundary)
vendor::go_consumer_sync already knew how to find the workspace for its
vendor/modules.txt audit. It now:
- loads the
go.work in effect once: GOWORK from the environment or from
go env -w, otherwise the nearest go.work at or above the project. It
reads go.work's replaces, its use members and each member's replaces;
- gets
workspace_conflict / replace_refusal, which decide which of the
three cases applies to a module@version replace, matching go's own rules:
- any go.work replace of the module path drops every member replace, unless
go.work's own replace for this version sends it to the project's copy;
- membership is compared on resolved directories;
- targets are compared lexically, as go compares replacements;
- a hosted replace names the same Socket module in every member, so it
never conflicts.
Callers:
- Agent
apply (golang_local::apply_go_redirect) and vendor
(vendor::golang::vendor_go_module, new code vendor_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_entry refuses as well, as a second guard.
audit reports a replace that was already written as
go_workspace_replace_ineffective. As a result apply --check and
vendor --check fail, and apply, vendor and scan --mode hosted warn.
vex leaves the patch out with the new omission tag
go_workspace_override, plus the warning.
Docs: docs/ecosystems.md (new "Go workspaces (go.work)" section) and the
golang entry in CLI_CONTRACT.md. No wrapper changes: the npm, pypi and gem
wrappers only dispatch to the binary.
Per-issue tests (red on main → green)
The three e2e tests below use the real go toolchain with a hermetic file
GOPROXY. On main (fix stashed) all three fail at the first assertion: apply
exits 0. With the fix they pass.
- 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:
e2e_golang_build::go_work_replace_overrides_the_socket_replace.
A go.work replace to ../fork makes apply --dry-run, apply and a re-run
refuse. go.mod is untouched, no copy is left, and the build still links
FORK. For a replace written before the go.work: apply --check exits 1 and
vex omits it with go_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).
- Go apply writes its replace into a root go.mod that go.work doesn't
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
--check and is omitted from VEX. Positive control: after use (. ./svc)
the svc build prints PATCHED and --check passes. Unit test:
go_work_that_does_not_use_the_project_makes_the_replace_inert (ancestor
go.work, and every spelling of the member path).
- 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:
e2e_golang_build::go_work_member_replaces_must_not_conflict.
Applying in member a patches the workspace. Applying in b refuses, and
the workspace still builds PATCHED. Replaces written in both members before
the go.work existed reproduce "conflicting replacements", fail --check in
both members and are omitted from VEX. Unit test:
member_replaces_of_one_module_version_must_agree (same target, other
version and hosted replaces don't conflict).
- Also:
go_work_replace_is_judged_by_where_it_points (from review) and
go_mod_edit::test_ensure_refuses_a_replace_go_work_defeats (all three
cases, dry run included, go.mod unchanged).
Commands run
cargo fmt --all -- --check: ok
cargo clippy --workspace --all-features -- -D warnings: ok
cargo 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 full cargo test --workspace --all-features build ran out of the sandbox's disk allowance, so CI runs the rest
cargo test -p socket-patch-cli --all-features --lib --bins: 911 passed
cargo 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 pass
SOCKET_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 high findings
Fixed in 1413d3c:
- The go.work exemption was keyed on
socket_owned; it now compares the
actual target.
- When go.work's own replace was exempted, member checks still ran.
- The check ran after the copy, which skipped dry runs and left orphan
copies.
vendor re-runs used a misleading error code.
go env -w GOWORK was ignored. Bugbot then pointed out that my own env-file reader mishandled an empty GOWORK and skipped the regular-file guard; e91342d switches to the shared utils::go_env.
- Each replace caused another read of the workspace (N×M reads), duplicating
workspace_replaces.
- Symlink canonicalisation of targets didn't match go's lexical comparison.
Not fixed: the hosted writer doesn't refuse. scan --mode hosted rewrites
through the pure, in-memory rewrite_golang, which only sees project-relative
file contents and can't look at an ancestor or GOWORK go.work. Hosted mode
is covered by the post-write warning, the --check failure and the VEX
omission, 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
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Assisted-by: Claude Code:claude-opus-5-5
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
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
cursor
Bot
left a comment
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stale Bugbot comment from a previous run.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
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
cursor[bot] reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ 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.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Ready for review at e91342d.
- CI: all checks green on the head (35 success, rest skipped); no main-wide failures.
- Bugbot: reviewed
e91342d, no findings (the two earlier go_consumer_sync threads are resolved and outdated).
- Mergeable with
main (41659c1), no CHANGELOG.md change.
Worth a look: Go replace writers and checks now consult the go.work in effect (including GOWORK from go env), so a replace that go.work overrides or ignores no longer counts as applied.
Generated by Claude Code
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Oct 12, 2026
github-merge-queue Bot
pushed a commit
that referenced
this pull request
Oct 13, 2026
* ci(release): publish npm from release.yml, not publish-npm.yml
Every socket-patch npm package's trusted publisher is registered against
release.yml, and npm allows only one publisher per package. The 5.0.0
release dispatched publish-npm.yml, whose OIDC token named that file, so
npm rejected it with E401 before staging anything.
Move the npm leg back inline as release.yml's npm-publish job (same steps
as publish-npm.yml) and delete publish-npm.yml, so the existing
registration keeps working. Add an `npm-retry-version` input that skips
the version gate, build, tag, GitHub release and crates.io jobs and
re-stages npm from an existing release's assets; this retries 5.0.0 now
and replaces the manual Publish npm dispatch. crates.io stays dispatched
through publish-cargo.yml, since crates.io allows several publishers per
crate.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
* chore(release): fold 22 more PRs into the 5.0.0 notes
Cover the PRs merged to main after #1432's base (1779317..4cb3643):
Cargo source replacement and registry = "crates-io" (#338/#455, #386),
pnpm lockfile=false (#1074), berry removed descriptors (#1435), yarnrc
${VAR} reads (#1100), CRLF .npmrc append (#1196), Composer vendor-dir
cascade and global home (#439/#686, #586), Go +incompatible vex (#484),
gem moved BUNDLE_GEMFILE and commented Gemfile revert (#952, #988),
Poetry source = "pypi" (#1282) and Hatch path characters (#547).
No entries for #1411 (macOS fix to code #1393 added in this release;
the #1393 entry already describes the intended behavior), #1426 (perf
regression introduced and fixed within 5.0.0) or the CI/test/bench PRs.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
* chore(release): cover #1419, #1434, #1440, #1442, #1444 in the 5.0.0 notes
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
* chore(release): 5.0.1
Cut [Unreleased] into [5.0.1] — 2026-10-13 and stamp 5.0.1 with
scripts/version-sync.sh. The 5.0.1 notes cover main through 501c6d9
(#1449), i.e. every PR after the v5.0.0 tag (6ad6add): the npm
publish fix (5.0.0 never reached npm or crates.io, 5.0.1 is the first
v5 there), #1465, #759, #1460, #1462, #1445, #869, #1447, #1450,
#1467, and #1449 under Maintenance. CI/test-only PRs (#1448, #1452,
#1454, #1457, #1459) have no entries.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
* chore(release): 5.0.1 notes for PRs merged since the cut
Covers #1469, #1470, #1474, #1475, #1478, #1479, #1480 and #1482; #1484 is
CI-only. The notes now cover main through 285a5e8.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Labels
Ready for review
Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review
Milestone
No milestone
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.
|