Skip to content

Fix Go patches ignoring GOFLAGS=-modfile (#1799) - #1800

Merged
Mikola Lysenko (mikolalysenko) merged 4 commits into
mainfrom
agent/fix-go-goflags-modfile
Oct 29, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 4 commits into
mainfrom
agent/fix-go-goflags-modfile

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 #1799

Summary

If GOFLAGS contains -modfile=<file>, go builds from <file> and its .sum and ignores the replaces in go.mod. This works whether GOFLAGS is set in the environment or with go env -w. Before this change, socket-patch still wrote its replace into go.mod, apply reported the patch applied, apply --check said it was in sync, and vex attested not_affected. The build linked the unpatched module. Now socket-patch refuses to write the replace and refuses to attest it, the same way it already handles a go.work that overrides the replace.

Root cause

socket-patch never read GOFLAGS. Every Go writer (agent apply, vendor, scan --mode hosted) edited go.mod/go.sum, and both --check and VEX discovery read only those files.

Fix

  • utils::go_env::goflags_modfile reads -modfile from GOFLAGS the way go's InitGOFLAGS does:
    • fields are split as quoted.Split splits them;
    • flags may be written -flag or --flag;
    • the last -modfile wins, and -modfile= means go's default.
    • It also reports a setting go refuses outright: a non-flag field, a bare -modfile, or an unterminated quote.
  • go_workspace::modfile_setting / modfile_conflict return a new WorkspaceConflict::ModFile in these cases:
    • -modfile names anything other than the project's go.mod. A relative path is taken from the project directory.
    • A go.work is in effect, where go refuses any -modfile.
    • GOFLAGS itself is one go refuses.
  • That conflict goes through the gates that already fail closed on a go.work override. No new codes:
    • go_consumer_sync::replace_refusal: agent apply and vendor write nothing.
    • go_consumer_sync::audit / workspace_audit: apply --check and vendor --check fail with go_workspace_replace_ineffective, and vex omits the patch with go_workspace_override. This covers go.mod replaces and root go.work replaces. The message names go.work for the latter.
    • VEX discovery withholds every Go ref as patched_ref_invalid, as it does for a foreign GOWORK. The probe runs only when GOFLAGS sets -modfile (or is one go refuses). It declares the files it reads through disk_root_reading, so a tracked hosted pass keeps its read recording.
    • The hosted engine reads the setting for on-disk projects and passes it as HostedGoWorkspace.modfile. The rewriter then refuses with redirect_golang_workspace_conflict.
  • CLI_CONTRACT.md and docs/ecosystems.md document the behavior.
  • No wrapper changes are needed. npm/, pypi/ and gem/ only dispatch to the binary.

Tests (red → green)

Case Test before after
#1799 end to end, real go 1.24.7: the dry run and the real apply refuse and write nothing; a replace written earlier fails --check and is omitted from vex with -modfile from the env or a GOENV file; -modfile=go.mod stays in sync e2e_golang_build::goflags_modfile_keeps_the_go_mod_replace_from_the_build fails on main (apply exits 0) pass
GOFLAGS parsing: last wins, quoting, --, refused settings go_env::goflags_modfile_reads_the_last_modfile n/a (new) pass
Own go.mod (relative/absolute/./) vs other file; workspace mode; relative GOWORK go_workspace::goflags_modfile_conflicts_unless_it_names_the_own_go_mod n/a pass
VEX discovery withholds refs discover::golang::goflags_modfile_naming_another_go_mod_attests_nothing n/a pass
Tracked snapshot keeps recording under -modfile (Bugbot) discover::golang::goflags_modfile_probe_keeps_a_snapshot_recording fails at 0b99e93 pass
Hosted rewriter refuses the pin redirect::golang_goflags_modfile_refuses_the_pin n/a pass

Per-issue checklist:

Local runs (0b99e93, then re-run at 801d50f):

  • cargo fmt --all -- --check and cargo clippy --workspace --all-features -- -D warnings: clean.
  • cargo test -p socket-patch-core --all-features --lib: 6928 passed. 4 failed, all chmod-based write-failure tests that can't fail as root in this container: copy_tree::relax_loop…, vlt_heal::an_unremovable…, pypi_poetry::wire_write_failure… and pypi_requirements::wire_failure…. None of them touch Go. hosted::engine::tests::a_writing_pass_hands_back_the_discovery_of_its_writes passes.
  • socket-patch-core integration suites: 32 suites, 656 passed.
  • Go e2e with SOCKET_PATCH_GO_E2E_REQUIRED=1 --include-ignored (go 1.24.7): e2e_golang_build 30, e2e_golang_workspace_build 28, e2e_golang_hosted_build 25, e2e_golang_hosted_state 30, e2e_vendor_golang_build 44, e2e_golang 23. All pass.
  • CLI lib (936), plus e2e_vex, e2e_vex_redirect, e2e_vex_vendor, hosted_memory_engine, hosted_memory_parity, in_process_remote_ecosystems_apply, in_process_rollback_all_ecosystems, in_process_vendor, cli_apply_silent and ecosystem_dispatch_e2e: all pass.
  • I didn't finish a full cargo test --workspace locally because the sandbox disk filled while linking every CLI test binary. CI runs it.

/code-review high

Fixed in 0b99e93:

  • VEX discovery took a raw disk_root() read.
  • In-memory discovery disagreed with the hosted engine. Both now skip in-memory projects.
  • The quote splitting didn't match go's quoted.Split.
  • Non-flag GOFLAGS fields weren't treated as a setting go refuses.
  • The message named go.mod for a go.work replace.
  • A relative GOWORK was counted as workspace mode.
  • The conflict was converted back and forth to a string.

Not changed:

  • Fold the gate into go_workspace::load/Loaded. Loaded describes the go.work in effect, and -modfile matters in module mode too. Mapping it to Loaded::Invalid/None would change every resolve() != GoWork::None "is this a workspace" check (selection gates, modules.txt location). I kept the rule in one function (modfile_setting_with) and called it at the existing fail-closed boundaries.
  • go.work read twice in audit, and GOFLAGS re-read per module. Each is one small file read per module, only on --check/apply runs. I didn't think threading a resolved setting through every signature was worth it.

Bugbot

  • 0b99e93: "GOFLAGS voids hosted discovery recording" (-modfile=go.mod still took the raw root read). Fixed in 801d50f, with a regression test.

https://claude.ai/code/session_011f9aePoQ9gtsKv1zP8Rxsg


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
With -modfile in GOFLAGS (set in the environment or with go env -w),
go builds from that file and ignores go.mod's replaces. socket-patch
never read GOFLAGS, so apply wrote a go.mod replace and reported the
patch applied, apply --check called it in sync, and vex attested
not_affected while the build linked the unpatched module.

GOFLAGS is now read the way go reads it. A -modfile naming any file
other than the project's go.mod (or any -modfile in workspace mode,
which go refuses) is treated like a go.work that overrides the
replace: apply, vendor and scan --mode hosted write nothing, a
replace already written fails apply --check and vendor --check, and
vex attests none of the project's Go patches.

Fixes #1799.

Assisted-by: Claude Code:claude-opus-5-5
Review fixes for the -modfile gate:

- A GOFLAGS that go refuses (a non-flag field, a bare -modfile, an
  unterminated quote) is reported as such, and a quoted field may run
  straight into the next one, as in go's own parser.
- VEX discovery only looks at the project root when GOFLAGS names
  -modfile, so hosted passes keep their read-tracking, and it skips
  in-memory projects, as the hosted engine does.
- A relative GOWORK is go's own refusal, not workspace mode, so it
  no longer hides behind a -modfile message.
- apply --check names go.work, not go.mod, for a go.work replace.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 28, 2026 23:09
@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.

Stale Bugbot comment from a previous run.

The VEX -modfile check took the project root through a raw read
whenever GOFLAGS named -modfile, even -modfile=go.mod, which voided a
tracked snapshot's recording for every hosted writing pass. The
check now declares the files it reads (the go.work candidates,
go.mod and the named file), so the recording survives.

Assisted-by: Claude Code:claude-opus-5-5
@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 801d50f. Configure here.

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

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: labeled Ready for review at 801d50f.

  • CI: 50/50 check runs on 801d50f green or skipped (31 success, 19 skipped). Mergeable, no conflicts, and no main-wide failures.
  • Bugbot: reviewed 801d50f and found no new issues. The one earlier thread (vex/discover/golang.rs) is resolved.
  • For reviewers: this PR adds GOFLAGS -modfile parsing (utils::go_env::goflags_modfile), which mirrors go's InitGOFLAGS. It also adds the new WorkspaceConflict::ModFile refusal path across apply, vendor, hosted scan, --check and VEX.
  • Slack: not announced this run, because posting to Slack wasn't available.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief

What it does. When GOFLAGS (from the env or go env -w) sets -modfile to anything other than the project's own go.mod, sets any -modfile while a go.work is active, or is malformed in a way go refuses, go ignores the Socket replace in go.mod. In those cases socket-patch now refuses to write the replace (agent apply, vendor, the hosted rewriter). --check reports go_workspace_replace_ineffective, and vex omits the patch instead of attesting it. Before this PR all of them reported success while the build linked unpatched code.

Risk: low. The PR only adds refusals and never adds writes. Every gate does nothing unless GOFLAGS sets -modfile or is malformed. I checked the GOFLAGS parsing against Go 1.24.7's own InitGOFLAGS/quoted.Split source and it matches: last -modfile wins, -modfile= means the default, the two-field -modfile x form is refused, and -mod is not confused with -modfile.

Look here

Verified

  • I read the full diff against the Go 1.24.7 source.
  • These unit tests pass locally: the new go_env, go_workspace and discover::golang tests, and the existing go_consumer_sync suite. The new tests exercise code that does not exist on main.
  • CI is 50/50 green on 801d50f, including ci-ok and clippy. Bugbot passed, and its one finding (read-tracking under -modfile=go.mod) was fixed in 801d50f with a regression test.
  • CHANGELOG.md is untouched.

Changes I made. None.

Notes (non-blocking).

  • A relative -modfile is resolved from the project root, while go resolves it from its own working directory. If go runs from a subdirectory, go itself fails loudly, so the build is never silently wrong. docs/ecosystems.md documents this.
  • engine.rs:565 stats the files without declaring them as reads, unlike the VEX probe. This is cosmetic.

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


Generated by Claude Code

Merged via the queue into main with commit 04e3c3a Oct 29, 2026
50 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-go-goflags-modfile branch October 29, 2026 13:40
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

3 participants