Skip to content

star lint go reports 'go.mod not tidy' for any uncommitted go.mod edit #686

Closed

Description

@David-Noble-at-work

Epic: #452
Related: #670 (CI must be a strict superset of the local checks)

star lint go reports "go.mod not tidy" for any uncommitted go.mod or go.sum change, whether or not
go mod tidy produced it.

The defect

cmd/star/provider/lint/provider.go:396:

func checkModTidy(ctx context.Context) (tidy bool, detail string) {
	tidyCmd := exec.CommandContext(ctx, "go", "mod", "tidy")
	if output, err := tidyCmd.CombinedOutput(); err != nil {
		return false, fmt.Sprintf("go mod tidy failed: %s\n%s", err, string(output))
	}
	diffCmd := exec.CommandContext(ctx, "git", "diff", "--exit-code", "go.mod", "go.sum")
	if output, err := diffCmd.CombinedOutput(); err != nil {
		return false, fmt.Sprintf("go.mod or go.sum not tidy:\n%s", string(output))
	}
	return true, ""
}

It runs go mod tidy, then compares the result to HEAD rather than to the pre-tidy state. The two are the
same thing only in a clean tree.

Reproduction, observed 2026-08-25

Bumping the go directive from 1.26.0 to 1.27.0 (uncommitted), then make lint:

[star] [✖] go.mod is not tidy
[star] [+]   -go 1.26.0
[star] [+]   +go 1.27.0
[star] [✔] No golangci-lint issues found
[star] [✖] Go lint failed: go.mod not tidy

The diff shown is the developer's own edit. go mod tidy produced zero changes — verified by snapshotting
go.mod and go.sum before the run and diffing after: no go.mod change, no go.sum churn.

Why CI never sees it

CI checks out a clean tree, so go.mod matches HEAD and the only diff git diff can report is one go mod tidy
actually made. The check is correct there and wrong everywhere else — which is precisely the workflow a developer
has: edit go.mod, run the gate, get a false failure until the edit is committed.

It also means the check cannot be satisfied while the edit is uncommitted, so the gate blocks the commit that
would satisfy it.

The fix

Compare against the pre-tidy state, not HEAD: copy go.mod and go.sum, run go mod tidy, diff the copies
against the results, restore on mismatch. That works in a dirty tree, in CI, and outside a git repository —
the current implementation requires all three of a git repo, a clean tree, and a committed baseline.

Severity

It is a false failure in the gate developers are now required to run: make lint routes through
star lint go as of 2026-08-25, so this blocks any workflow that touches go.mod before committing.

Activity

  1. added
    bugSomething isn't working
    Area:TestingTest coverage and verification
    Epic:QualityCoverageCode quality, coverage, and the lint gates
    Severity:HighA supported path is broken with no workaround
    on Aug 26, 2026
  2. David-Noble-at-work commented on Aug 26, 2026

    @David-Noble-at-work
    ContributorAuthor

    Fixed in cmd/star/provider/lint/provider.go.

    The change

    checkModTidy now compares the module files against their own pre-tidy content, never against HEAD:

    1. snapshotModuleFiles() records the bytes, mode, and existence of go.mod and go.sum
    2. go mod tidy runs
    3. A second snapshot is compared byte-for-byte against the first
    4. On mismatch the originals are restored, so a failing lint leaves the tree exactly as it found it

    That last step is new behaviour. The old implementation ran tidy for real and left whatever it wrote in place — a
    linter that silently rewrote your source. It now reports without mutating.

    Two other properties fall out: the check no longer needs a git repository, and no longer needs a committed
    baseline.

    Verified both directions

    The false positive is gone. With the uncommitted go 1.26.0 → go 1.27.0 bump still in the tree — the exact
    condition that produced this issue:

    [star] [+] Checking go.mod tidy...
    [star] [✔] go.mod is tidy
    [star] [✔] All Go lint checks passed
    make lint exit: 0
    

    Genuine untidiness is still caught. Injecting an unused github.com/google/uuid v1.6.0 require:

    [star] [✖] go.mod is not tidy
    [star] [+]   go mod tidy would rewrite go.mod — run `go mod tidy` and commit the result
    make lint exit: 2
    

    And the restore holds. After that failing run, the injected require was still present in go.mod — the check
    reported the problem rather than quietly fixing it. Confirmed by grep, then reverted.

    Context

    Found while routing make lint through star lint go so the local gate matches CI (#670). Routing exposed that
    star lint go is golangci-lint plus a tidy gate that local make lint never ran — a third divergence
    between the two gates, alongside the golangci-lint version skew and the GOOS coverage gap.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Area:TestingTest coverage and verificationEpic:QualityCoverageCode quality, coverage, and the lint gatesPriority:P2Scheduled, not urgentSeverity:HighA supported path is broken with no workaroundbugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions