Skip to content

Fix Go version gate ignoring go.work members (#1751) - #1752

Merged
Mikola Lysenko (mikolalysenko) merged 4 commits into
mainfrom
agent/fix-go-workspace-version-selection
Oct 28, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 4 commits into
mainfrom
agent/fix-go-workspace-version-selection

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #1751

Summary

In a go.work workspace, Go builds the highest version of a module that any use member requires. The Go version check behind agent apply, apply --check, vendor and vex read only the current module's go.mod/go.sum. So when another member required a newer version, apply wrote a replace the build ignored, apply --check said "in sync", and vex attested not_affected while the workspace linked the unpatched module.

Root cause

go_build_list::project_selection trusted the member's own require. In workspace mode it only relaxed NotInGraph to Unknown, never looking at what other members select.

Fix

  • project_selection now also reads every use member's go.mod/go.sum (go_consumer_sync::workspace_member_dirs, which shares go.work parsing with the existing conflict check). It weighs the versions they prove the way MVS does: a higher version anywhere is Other(v), and the patched version proven anywhere is Selected. When every member proves a lower version and none is undecided, that version is reported.
  • Every caller picks this up: agent apply and vendor refuse, apply --check fails, and vex omits the patch with go_version_not_selected. That includes hosted pins, since vex checks every Socket-owned replace.
  • docs/ecosystems.md says which commands read the other members.

Follow-up (not in this PR)

The hosted write gate (rewrite_golang in patch/redirect/mod.rs) is an in-memory rewriter that only sees the root module's files, so scan --mode hosted in a member can still write a pin another member outranks. vex no longer attests it. Tracked in #1753. Fixing it means feeding member files into the hosted read set, which is a separate change to the hosted pipeline.

Test evidence

Issue Test Without fix With fix
#1751 go_build_list::tests::workspace_member_requiring_a_higher_version_wins FAIL (Selected) ok
#1751 go_build_list::tests::workspace_lower_or_unused_members_do_not_change_the_selection FAIL ok
#1751 go_build_list::tests::workspace_lower_versions_decide_only_without_unknown_members n/a (review follow-up) ok
#1751 e2e_golang_build::go_work_member_requiring_a_newer_version_makes_the_replace_inert (real go 1.24.7: apply refuses, --check exits 1, vex omits with go_version_not_selected, positive control) FAIL (apply exit 0) ok

Commands run locally:

  • cargo fmt --all -- --check: ok

  • cargo clippy --workspace --all-features -- -D warnings: ok

  • SOCKET_PATCH_GO_E2E_REQUIRED=1 cargo test --workspace --all-features --no-fail-fast: 15078 passed, 12 failed. All Go, vendor and vex suites pass (e2e_golang 23, e2e_golang_build 28, e2e_golang_hosted_build 22, e2e_golang_hosted_state 29, e2e_golang_workspace_build 16, e2e_vendor_golang_build 38, vendor 151, vex 47). The 12 failures are outside this diff and come from this container:

    • 9 write-failure tests (copy_tree, vlt_heal, pypi_poetry/requirements wire failures, in_process_redirect, repair) rely on chmod denying writes, and the container runs as root.
    • 3 in e2e_bun_lockb run with local bun 1.4.4, while CI pins 1.4.2.

    CI runs these suites on the PR.

  • CI on ac7071b: 32 success, 50 skipped by path filters, Bugbot clean. The Go patch compatibility workflow (multi-Go-release matrix) is disabled in this repo, so it did not run and workflow_dispatch returns 422. The same suites passed locally on go 1.24.7 (above).

  • No wrapper (npm/, pypi/, gem/) changes: they only dispatch to the binary.

/code-review high findings not fixed

  • Hosted rewriter still single-module: out of scope, tracked in Hosted Go scan in a go.work member pins a version another member outranks #1753 (see above).
  • A newer version that enters only through another member's dependency, which that member doesn't import: project_selection reads go.mod/go.sum evidence only, the same limit as the single-module check (a pruned graph trusts require). Seeing it would mean reading dependency go.mod files from the module cache.
  • Re-reading member files on each call: one go.mod/go.sum read per member per patched module, offline. That is negligible next to apply/vex I/O, so caching isn't worth the code.
  • Unit tests read the ambient GOWORK: the same dependency as the existing go.work tests in go_consumer_sync. CI doesn't set it.

https://claude.ai/code/session_011f9aePrKGvQCvZ5cTmj8ZB


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
In a go.work workspace Go builds the highest version any member
requires. The version check behind apply, apply --check, vendor and vex
read only the current module's go.mod, so when another member required
a newer version, apply wrote a replace the build ignored, apply --check
said "in sync" and vex attested not_affected for a module the workspace
still built unpatched.

The check now reads every `use` member's go.mod and go.sum and treats a
higher version anywhere in the workspace as the one built. apply and
vendor refuse, apply --check fails and vex leaves the patch out
(go_version_not_selected). Fixes #1751.

Assisted-by: Claude Code:claude-opus-5-5
When every go.work member proves a version below the patched one and
none is undecided, the workspace builds the highest of them, so the
refusal now names that version instead of this module's own or
"unknown". Reading go.work's members is shared with the conflict check,
and the Go workspace docs say which commands read the other members.

Assisted-by: Claude Code:claude-opus-5-5
The workspace member list now answers whether a go.work is in effect,
so the separate check has no callers left.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 27, 2026 23:07
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 ac7071b. Configure here.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 28, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: labeled Ready for review at head ac7071b.

  • CI: 82/82 checks on ac7071b passed or skipped (32 success, 50 skipped by path filters). Mergeable with main; no main-wide failures.
  • Bugbot: reviewed ac7071b, no issues found. No open review threads.
  • Reviewer focus: go_build_list::project_selection now weighs every go.work member's go.mod/go.sum MVS-style; the hosted write gate is left to Hosted Go scan in a go.work member pins a version another member outranks #1753.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief

What it does. In a go.work workspace, Go builds the highest version of a module that any use member requires. The Go version gate behind apply, apply --check, vendor and vex now reads every member's go.mod/go.sum. Before, it read only the current module's files, so a member requiring a newer version made the Socket replace inert without anyone noticing. Fixes #1751.

Risk: low. The change is limited to project_selection. Its callers act only on Selection::Other, so the most it can do is refuse or flag more. It can't attest anything new. The read is offline: one go.mod/go.sum per member. Outside a workspace nothing changes.

Look here

  • go_build_list.rs:117-151:`` the MVS-style merge. A higher version anywhere gives Other, and a lower one counts only when no member is `Unknown`.
  • go_consumer_sync.rs:304:``` workspace_member_dirs, which shares go.work parsing with the existing conflict check.
  • vex.rs:1003: the vex caller, unchanged. It drops only proven Other.
  • e2e_golang_build.rs:992: real-go e2e covering apply refusing, --check exiting 1, vex omitting the patch, and a positive control.

Verified

  • Read the full diff and traced every project_selection caller (vex, golang_local drift and write gates).
  • cargo test -p socket-patch-core --lib go_build_list: 10/10 passed.
  • SOCKET_PATCH_GO_E2E_REQUIRED=1 cargo test -p socket-patch-cli --test e2e_golang_build go_work on go 1.24.7: 4/4 passed. CI doesn't run the Go e2e because the Go compatibility workflow is disabled, so this local run is the only e2e evidence.
  • CI on ac7071b: 32 passed, 50 skipped by path filters, 0 failed. Bugbot clean. CHANGELOG.md untouched.

Changes I made. None.

Open questions. None blocking. Known limits are in the PR description: the hosted rewriter is still single-module (#1753, and vex no longer attests those pins), and a newer version that enters only through a member's dependency isn't seen.

Auto-merge is armed, so approving sends this straight to the merge queue.


Generated by Claude Code

Merged via the queue into main with commit 68d2a49 Oct 28, 2026
82 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-go-workspace-version-selection branch October 28, 2026 14:02
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Oct 28, 2026
Assisted-by: Claude Code:claude-opus-5-5
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Oct 28, 2026
Main landed lighter fixes for the same go.work gaps this PR models in
full: #1752 (version gate reads every member), #1753 (hosted pins
outranked by another member), #1767 (stale go.work vendor warning) and
the squashed #1748. Resolve them onto this PR's GoWorkspace model
instead of keeping two workspace readers:

- go_build_list: keep project_selection on GoWorkspace. Drop main's
  ModuleEvidence / workspace_selection / other_workspace_members and
  its three on-disk selection tests: GoWorkspace::selection covers
  those cases from the module graph (go_workspace tests), and without
  a module cache it answers Unknown by design.
- hosted: drop #1753's go_members plumbing (engine RewriteOptions
  closure, rewrite_golang argument, CLI wiring). The PR's
  HostedGoWorkspace already refuses a pin another member outranks.
  Main's e2e test for it now seeds the module cache the workspace
  graph reads, and passes, including the rescan that removes the pin.
- go_consumer_sync: port #1767's modules.txt audit (per-member replace
  resolution, member_owned, unreadable members skipped) onto
  go_workspace::load and its shared same_dir_key / lexical_target.
- vex discover, CLI_CONTRACT, e2e_golang_hosted_build: keep the PR's
  stricter rules (a zip pin is checked against the workspace selection
  too; sum-file zip lines are no evidence of it).

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

4 participants