Skip to content

Isolate golangci-lint cache per worktree to stop cross-worktree contamination#5925

Open
janniklasrose wants to merge 1 commit into
mainfrom
janniklasrose/lint-cache-per-worktree
Open

Isolate golangci-lint cache per worktree to stop cross-worktree contamination#5925
janniklasrose wants to merge 1 commit into
mainfrom
janniklasrose/lint-cache-per-worktree

Conversation

@janniklasrose

Copy link
Copy Markdown
Contributor

Problem

Running lint (and thus ./task test's lint step / dev loop) from a worktree under ~/pub/cli.worktrees/* intermittently fails with phantom lint issues pointing at file paths in other worktrees — including deleted ones (failed to parse file … no such file or directory).

golangci-lint keys its results cache on file contents but stores each cached issue's absolute path. Two worktrees with byte-identical packages (the near-static tools/ module is the usual culprit) get a cross-worktree cache hit in the machine-global ~/Library/Caches/golangci-lint, so the second worktree inherits the first's absolute paths. The //nolint post-processor then re-opens those paths to apply suppressions; when the other worktree has been deleted the open fails, so suppressed issues (e.g. dupword) leak through as phantom failures.

The content-addressable cache keys added in golangci-lint v2.12.0 (which the Taskfile comment credited) only fixed the concurrent case where both worktrees are live. The deleted-worktree case still reproduces on the pinned v2.12.2 — verified with throwaway worktrees: lint tools/ in worktree A → delete A → lint identical tools/ in worktree B ⇒ 3 phantom dupword failures before this change, 0 after.

Fix

Set GOLANGCI_LINT_CACHE to a per-worktree path ({{.ROOT_DIR}}/.task/golangci-lint) in a top-level env: block, so the cache lives under each worktree's already-gitignored .task/. No two worktrees can share it, making contamination structurally impossible.

  • Applies to every golangci-lint invocation (run, fmt, lintdiff) via the top-level env:.
  • Cost is negligible (the results cache is a few MB; the big content-addressed go-build cache stays shared).
  • CI is unaffected: the lint job runs a single clean checkout and never persists this directory (setup-go caches only the Go build cache, keyed on go.sum + .golangci.yaml).

Testing

Reproduced the failure and confirmed the fix end-to-end using disposable detached worktrees (create A → ./task lint-go-tools → delete A → lint identical B): phantom dupword failures before, 0 issues after, with B using its own .task/golangci-lint.

This pull request and its description were written by Isaac.

…mination

golangci-lint keys its results cache on file *contents* but stores each
cached issue's *absolute* path. Two worktrees with byte-identical packages
(the near-static tools/ module is the usual culprit) get a cross-worktree
cache hit, so the second inherits the first's absolute paths. The //nolint
processor then re-opens those paths to apply suppressions; when the other
worktree has been deleted the open fails, so suppressed issues (e.g.
dupword) leak through as phantom failures pointing at a gone path, plus a
wall of "no such file or directory" warnings.

The content-addressable cache keys added in v2.12.0 only fixed the
concurrent case (both worktrees live). The deleted-worktree case still
reproduces on the pinned v2.12.2. Rooting GOLANGCI_LINT_CACHE under the
gitignored per-worktree .task/ makes contamination structurally impossible.

CI is unaffected: it runs a single checkout and never persists this
directory (setup-go caches only the Go build cache).

Co-authored-by: Isaac
@janniklasrose janniklasrose requested a review from pietern July 14, 2026 15:02
@github-actions

Copy link
Copy Markdown
Contributor

Waiting for approval

Based on git history, these people are best suited to review:

  • @pietern -- recent work in ./

Eligible reviewers: @andrewnester, @anton-107, @denik, @lennartkats-db, @renaudhartert-db, @shreyas-goenka, @simonfaltum

Suggestions based on git history. See OWNERS for ownership rules.

@eng-dev-ecosystem-bot

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: f1f1ad9

Run: 29343577706

Env 🔄​flaky 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
💚​ aws linux 4 4 230 1087 5:34
💚​ aws windows 4 4 232 1085 7:16
💚​ aws-ucws linux 4 4 316 1004 7:39
💚​ aws-ucws windows 4 4 318 1002 7:12
💚​ azure linux 4 4 230 1086 5:16
🔄​ azure windows 1 3 4 232 1084 8:08
💚​ azure-ucws linux 4 4 318 1001 6:06
💚​ azure-ucws windows 4 4 320 999 7:28
💚​ gcp linux 4 4 229 1088 5:02
💚​ gcp windows 4 4 231 1086 7:16
8 interesting tests: 4 SKIP, 3 RECOVERED, 1 flaky
Test Name aws linux aws windows aws-ucws linux aws-ucws windows azure linux azure windows azure-ucws linux azure-ucws windows gcp linux gcp windows
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/invariant/no_drift 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/ssh/connection 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🔄​ TestFetchRepositoryInfoAPI_FromRepo 💚​R 💚​R 💚​R 💚​R 💚​R 🔄​f 💚​R 💚​R 💚​R 💚​R
💚​ TestFetchRepositoryInfoAPI_FromRepo/root 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
💚​ TestFetchRepositoryInfoAPI_FromRepo/subdir 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
Top 10 slowest tests (at least 2 minutes):
duration env testname
6:20 gcp windows TestAccept
6:13 azure-ucws windows TestAccept
6:08 aws windows TestAccept
6:06 aws-ucws windows TestAccept
6:05 azure windows TestAccept
2:57 gcp linux TestAccept
2:54 aws linux TestAccept
2:52 azure linux TestAccept
2:52 azure-ucws linux TestAccept
2:46 aws-ucws linux TestAccept

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants