Skip to content

Fix hosted Go pins outranked by go.work members (#1753) - #1755

Merged
Mikola Lysenko (mikolalysenko) merged 8 commits into
mainfrom
agent/fix-go-hosted-workspace-members
Oct 28, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 8 commits into
mainfrom
agent/fix-go-hosted-workspace-members

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

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

Fixes #1753

#1752 has landed; this PR now targets main.

Summary

In a go.work workspace, Go builds the highest version of a module that any use member requires. scan --mode hosted (and get --mode hosted) read only the module they ran in. So in member a the scan pinned example.com/upstream v1.0.0 while member b's v1.1.0 is what gets built, and reported the module as redirected though the pin did nothing. #1752 already keeps vex from attesting such a pin. This PR stops the scan from writing it.

Root cause

rewrite_golang (patch/redirect/mod.rs) is an in-memory rewriter over the files the hosted engine reads, which are the root module's go.mod/go.sum only. Its version gate never saw the other members.

Fix

  • go_build_list: Fix Go version gate ignoring go.work members (#1751) #1752's MVS weighing moves out of project_selection into a pure workspace_selection, so the agent and hosted paths share one rule. New ModuleEvidence parses a member's go.mod/go.sum once. other_workspace_members reads every use member except the scanned one (matched with go_consumer_sync::same_dir_key, like the rest of the go.work code).
  • hosted::engine::RewriteOptions gains a lazy go_workspace resolver, the same pattern as cargo_outer. It is called only when a golang dep is rewritten. The CLI passes other_workspace_members(cwd). The in-memory engine has no project on disk and passes none.
  • rewrite_golang weighs those members. A pin another member outranks is refused with redirect_golang_version_mismatch ("another go.work workspace member makes the build select v1.1.0"). A hosted pin written earlier is removed on rescan, the same as when the module's own go.mod moves on. A module this go.mod omits but a member proves at the patched version now counts as in the graph.
  • docs/ecosystems.md: scan --mode hosted is now in the list of commands that read every member.

Test evidence

Issue Test Without fix With fix
#1753 e2e_golang_hosted_state::hosted_pin_another_go_work_member_outranks_is_refused (control pin, then member b moves to v1.1.0: rescan reports 0 pinned + mismatch and removes the go.mod/go.sum pin; a fresh project never gets it) FAIL: left: 1 right: 0, envelope "action":"applied" ok
#1753 patch::redirect::tests::golang_workspace_member_requiring_a_higher_version_skips n/a (new API) ok
#1753 patch::redirect::tests::golang_workspace_members_that_do_not_outrank_still_redirect n/a (new API) ok
#1753 vendor::go_build_list::tests::other_workspace_members_reads_every_member_but_this_one n/a (new API) ok

Red run: stashed the src/ changes, kept the e2e, cargo test -p socket-patch-cli --all-features --test e2e_golang_hosted_state -- go_work failed as above; restored, passed.

Local commands (go 1.24.7, SOCKET_PATCH_GO_E2E_REQUIRED=1):

  • cargo fmt --all -- --check: ok
  • cargo clippy --workspace --all-features -- -D warnings: ok
  • cargo test -p socket-patch-core --all-features --lib: 6806 passed, 4 failed. All 4 rely on chmod denying writes, which it doesn't here because the container runs as root (copy_tree symlinked root, vlt_heal unremovable lock, pypi_poetry/pypi_requirements wire failures). Fix Go version gate ignoring go.work members (#1751) #1752 saw the same.
  • cargo test -p socket-patch-cli --all-features for --lib --bins plus e2e_golang (23), e2e_golang_build (28), e2e_golang_hosted_build (22), e2e_golang_hosted_state (30), e2e_golang_workspace_build (16), e2e_vendor_golang_build (38), hosted_memory_engine, hosted_memory_parity, in_process_get_hosted_ecosystems, in_process_rollback_hosted, vendor (151) and vex (47): all pass.
  • I didn't run the full cargo test --workspace locally: linking every test binary ran this container out of disk. CI runs it.
  • No wrapper changes (npm/, pypi/, gem/ only dispatch to the binary).

/code-review high findings

Fixed in d882a61:

  • Each member's go.mod/go.sum was re-parsed once per patched dep. Now it is parsed once per scan (ModuleEvidence).
  • The scanned module was matched with a bare canonicalize. It now uses same_dir_key.
  • The not-in-graph warning blamed only this go.mod even when the workspace was weighed. It now says no member proves the version.
  • The e2e depended on the ambient GOWORK/GOENV. The hosted state tests now pin GOWORK=auto GOENV=off.

Not fixed, and why:

https://claude.ai/code/session_011f9aeQqMk6io8PkpNve53f


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
Assisted-by: Claude Code:claude-opus-5-5
In a go.work workspace the build uses the highest version any member
requires. `scan --mode hosted` only read the module it ran in, so in
member a it pinned v1.0.0 while member b's v1.1.0 is what gets built,
and reported the module as redirected though the pin did nothing.

The hosted Go rewriter now also weighs the other members' go.mod and
go.sum, the same way agent apply, apply --check and vex do. It refuses
such a pin with redirect_golang_version_mismatch, and a rescan removes
a pin it wrote before another member moved ahead. The in-memory engine
has no workspace on disk and is unchanged.

Fixes #1753

Assisted-by: Claude Code:claude-opus-5-5
Review follow-ups for the workspace check:

- Parse each member's go.mod and go.sum once, not once per patched
  module, so a large workspace does not slow the scan.
- Match the scanned module against the use list the way the rest of
  the go.work code does (same_dir_key).
- Say "no go.work member" in the not-in-graph warning when the
  workspace was weighed, instead of blaming this module alone.
- Pin GOWORK and GOENV in the hosted Go state tests so a machine
  setting GOWORK=off can't hide the workspace.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] coverage (1) failed on d882a61 in vendor::pypi_hatch::tests::hatch_on_an_absolute_path_entry_passes_the_version_gate (pypi_hatch_unsupported: the fake hatch it writes didn't answer the version probe). This PR doesn't touch the PyPI/Hatch code, and the test passes on main (b787454) and on #1754's head. The test writes an executable and runs it right away, which looks like the usual exec-while-open race under parallel tests. No fix exists yet. I'm re-running the job once. If it fails again, I'll treat it as real.


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 d882a61. 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 d882a61.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

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


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 any use member requires, but scan --mode hosted only read the scanned module's own go.mod/go.sum, so it wrote pins another member outranks (inert, yet reported as redirected). This PR gives the hosted Go rewriter the other members' go.mod/go.sum and refuses (and on rescan removes) such pins, reusing #1752's MVS rule via a shared workspace_selection.

Risk: low–medium. Narrow to the hosted Go rewriter; the member read is lazy (only when a golang dep is rewritten) and an empty member list keeps today's behavior exactly (in-memory/napi engine passes none). Medium only because it can now refuse pins that were written before; that is the intended fix.

Look here

Verified

  • Read the full diff against Fix Go version gate ignoring go.work members (#1751) #1752's branch (the two commits this PR adds). Checked the sync workspace_member_dirs_sync matches the async one (same workspace_file/use_dirs/normalize, honours GOWORK=off), that ModuleEvidence::selection gives the same answer as dir_selection for a member, and that the not-in-graph → Unknown widening only applies when members exist (same rule as Fix Go version gate ignoring go.work members (#1751) #1752).
  • Tests exercise the change: the e2e hosted_pin_another_go_work_member_outranks_is_refused (e2e_golang_hosted_state.rs:281) failed without the fix per the PR's red run; unit tests cover outranking, non-outranking and absent-from-this-go.mod members.
  • Ran locally: cargo test -p socket-patch-core --all-features --lib -- go_build_list golang_workspace: 13 passed.
  • CI on d882a61: 30/30 non-skipped checks green (ci-ok, clippy included). Bugbot: no issues on this head. No unresolved threads. CHANGELOG.md untouched.

Changes I made: none.

Open questions: none blocking. Follow-ups the PR already names (not in scope): go.work replace conflicts in the hosted path, go.work.sum, and the in-memory engine staying single-module (#1200).

Merge order: stacked on #1752 (base agent/fix-go-workspace-version-selection). Approve and merge #1752 first. Auto-merge is not armed here yet, because while the base is #1752's branch an approval would squash this into that branch rather than into main; I'll arm it once GitHub retargets this PR to main.


Generated by Claude Code

Base automatically changed from agent/fix-go-workspace-version-selection to main October 28, 2026 14:02
Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor

cursor Bot commented Oct 28, 2026

Copy link
Copy Markdown

Bugbot is paused — your team has an unpaid invoice

Bugbot is paused because your team has an unpaid invoice.

Pay it from the billing page in the Cursor dashboard and Bugbot resumes automatically.

(requestId: 37a821e2-708f-4f35-b414-cdace212661e)

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Bugbot can't review 920f2f1: Cursor says it is paused until the team pays an unpaid invoice (Cursor dashboard → Billing). 920f2f1 only merges main after #1752's squash. The PR's diff against main is byte-identical to the one on d882a61, which Bugbot reviewed with no findings. I'll re-trigger the review once Bugbot is back. This needs a human to settle the invoice.


Generated by Claude Code

Merged via the queue into main with commit 103a664 Oct 28, 2026
49 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-go-hosted-workspace-members branch October 28, 2026 14:56
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

Development

Successfully merging this pull request may close these issues.

Hosted Go scan in a go.work member pins a version another member outranks

3 participants