Skip to content

ci: keep gocyclo's filesystem walk out of node_modules and vendor - #1286

Draft
IAmJSD wants to merge 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-177
Draft

IAmJSD wants to merge 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-177

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

Root cause

ci.yml's lint job runs gocyclo -over 10 -ignore "_test\.go" ., and the Makefile's complexity and complexity-report targets run gocyclo ... .. All three walk the whole checkout. gocyclo v0.6.0 skips vendor/, testdata/ and directories starting with . or _, but not node_modules/. npm packages can ship Go source (frontend/node_modules/flatted/golang/pkg/flatted/flatted.go, with Stringify at complexity 32), so any run after npm ci reports third-party functions as findings. CI is not hit today only because the lint job doesn't install npm deps. Local runs and agents that copy the documented command are hit.

Fix

Add (^|/)(node_modules|vendor)/ to gocyclo's -ignore regex in:

  • .github/workflows/ci.yml (lint job, "Check cyclomatic complexity"): -ignore "_test\.go|(^|/)(node_modules|vendor)/"
  • Makefile complexity and complexity-report: -ignore '(^|/)(node_modules|vendor)/'. These targets keep their current behavior of including _test.go files.

gocyclo checks -ignore before it parses a file, so ignored files are never opened. The vendor/ part does nothing with gocyclo v0.6.0, which already skips vendor directories. It is there so the exclusion doesn't depend on that built-in behavior. The pattern is anchored to a whole path segment, so first-party directories such as internal/vendorutil/ or internal/my_node_modules/ are still checked (tested below).

I kept the . walk instead of switching to the issue's git ls-files form so the set of checked files stays exactly the same. A git ls-files list would also pick up tracked .go files under testdata/, .-prefixed or _-prefixed directories, which the walk skips today.

The pre-commit gocyclo hook needs no change. It lists files with git ls-files, and frontend/node_modules/ is gitignored. I confirmed it passes with a fake node_modules Go file present (below).

Refs #177

Verification

All of this is fixture evidence. I made a mktemp -d copy of the tree (git archive HEAD) and added a fake frontend/node_modules/flatted/golang/pkg/flatted/flatted.go with a complexity-13 function, plus a fake internal/fakevendor/vendor/x/x.go. No npm install was run. Go 1.26.9, gocyclo v0.6.0.

Files scanned, old command vs new. I traced each command with strace -f -e trace=openat and listed every .go file it opened:

command node_modules present clean tree
CI old gocyclo -over 10 -ignore "_test\.go" . 259 files, exit 1 (13 flatted Stringify frontend/node_modules/.../flatted.go:3:1) 258 files, exit 0
CI new ... -ignore "_test\.go|(^|/)(node_modules|vendor)/" . 258 files, exit 0 258 files, exit 0
Make old gocyclo -over 10 . / -top 20 . 914 files 913 files
Make new (with -ignore) 913 files 913 files
  • With node_modules present, diff old new shows exactly one line in each case: < frontend/node_modules/flatted/golang/pkg/flatted/flatted.go.
  • On the clean tree, the old and new file lists are identical for both CI and Make, and the stdout is byte-identical (cmp).
  • The new CI file list is identical to git ls-files '*.go' | grep -v _test.go on this branch (258 files), and to a find that prunes vendor, testdata, node_modules, and ./_-prefixed directories.
  • The fake vendor/ file was skipped by both the old and new commands, as expected from gocyclo's built-in skip list.

The workflow step itself, before and after. I extracted the "Check cyclomatic complexity" run: block from origin/main and from this branch with a YAML parser, then ran each with bash -e in the fixture tree:

  • old step, node_modules present: ❌ Found functions ... flatted Stringify ..., exit 1
  • new step, node_modules present: ✅ All functions have acceptable cyclomatic complexity (≤10), exit 0
  • both steps, clean tree: ✅, exit 0

The new regex still catches first-party code. I put the same complexity-13 function in internal/vendorutil/f.go and internal/my_node_modules/f.go. The new CI command reported both (exit 1) and still skipped the node_modules/ copy.

Make targets. In the fixture, the old make complexity listed the flatted finding and the new one did not (61 vs 60 findings; the other 60 are explained under "Out of scope"). make -n complexity complexity-report expands to the expected -ignore '(^|/)(node_modules|vendor)/' command lines.

Pre-commit hook. gocyclo -over 10 $(git ls-files "*.go" | grep -v _test.go | grep -v vendor/) exits 0 with the fake node_modules file present, so it is not affected.

Lint. actionlint v1.7.12 on .github/workflows/ci.yml: no findings. shellcheck 0.11.0 on the extracted step script: no findings.

Merge check. git merge-tree --write-tree against the heads of #1222, #1227 and #1119 and the fork branches fix/issue-1233 and fix/issue-353 reports no conflicts. Their ci.yml hunks are at lines 743+ and 1299+; #1227's Makefile hunks are at lines 11 and 159.

There is no committed regression test. The fix is a regex argument inside a workflow run: block and two Makefile recipes, and a test would have to parse it out of the YAML and Makefile. That is brittle, and it would need another CI step to run it. The evidence above is the fail-before/pass-after proof.

Combined-tree verification. This branch was tested together with the other queued branches on a local, never-pushed tree: queue/platform @ 936996ad = origin/main a14dc8f2 plus these heads, merged in this order:

Branch Tested head
fix/issue-337 ff3420c
fix/issue-169 7d75eb5
fix/issue-342 66b221f
fix/issue-310 5e11c21
fix/issue-326 5f0ea59
fix/issue-243 365e959
fix/issue-343 6c94036
fix/issue-312 d2848a8
fix/issue-316 acd60dd
fix/issue-339 75ea9b9
fix/issue-339-commitment-type 73bdb19
fix/issue-353 5d25fcd
fix/issue-313 3156bc4
fix/issue-357 53d9e58
fix/issue-368 0ba727d
fix/issue-419 61648c9
fix/issue-111 b04c372
fix/issue-686 0ad90b7
fix/issue-674 793095d
fix/issue-1055 4ec96f1
fix/issue-559 dfa9525
fix/issue-558 034eed4
fix/issue-874 1720539
fix/issue-666 1e120da
fix/issue-668 fb26345
fix/issue-568 ce04173
fix/issue-311 e54f250
fix/issue-177 0cbb88a
fix/issue-151 c21377c
fix/issue-166 41fb77b
fix/issue-27 2d67622
fix/issue-323 69288ae
fix/issue-59 36463e8
fix/issue-43 35e4069
fix/issue-158 6f76f8a
fix/issue-104 bb00491
fix/issue-55 971ef1b
fix/issue-361 89ac220
fix/issue-120 b458719
fix/issue-36 42bac6e
fix/issue-525 57c97dd
fix/issue-712 e5bcda0
fix/issue-72 6cc63ed

fix/issue-72 and fix/issue-312 conflict on one line in internal/api/handler_dashboard.go (both change the calculateCommitmentMetrics call). The tree used #72's side with #312's validated provider variable; whichever PR merges second takes that same resolution.

Linux, Go 1.26.9, AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true; Docker Postgres via testcontainers:

  • Passed at 936996ad: go build ./..., go vet ./..., go vet -tags integration ./... and the full go test -race -short -count=1 ./....
  • Static checks at 936996ad: golangci-lint v2.10.1 found 0 issues on every changed package, with and without --build-tags integration. tests/e2e is a separate module, so it was vetted under its own e2e tag. gocyclo -over 10 is silent on the changed non-test files.
  • Integration tests, every changed package, one package at a time (-tags integration -count=1, 60-minute local timeout because the VM was shared):
    • At 936996ad, these pass: internal/server, internal/credentials, internal/scheduler, internal/database/postgres/migrations, internal/purchase and internal/api.
    • internal/config passes except TestPostgresStoreDB_ResolveStaleRIExchange_ValidatesCloudCheck, which fails the same way on unmodified main (fixed by fix(config): compare resolve checked_at at the database's microsecond precision #1252).
    • These were run on the first build of this tree (26795376, same branches at earlier heads): internal/analytics, internal/auth, internal/commitmentopts, internal/database and cmd/lambda/clear-rate-limit all pass. The later fixes don't touch those packages.
  • What the combined run caught, each fixed on its own branch before this PR opened:
    • a compile break between fix/issue-104's test and fix/issue-326's httpToLambdaRequest signature change;
    • fix/issue-316 rejecting legacy-keyed plans;
    • fix/issue-120 versus main's global-filter test.
  • Secrets: git-secrets, using the repo's scripts/setup-git-secrets.sh patterns in a fresh repo with an isolated HOME, found nothing in the changed files, and a planted fake key was flagged. scripts/test-git-secrets-allowlist.sh: 137 PASS, 0 FAIL.
  • Nothing was applied to any cloud.

Out of scope (left for follow-up)

  • Audit finding A15-003 in the issue body (go vet ./... picks up node_modules). I reproduced it in the fixture: go list ./... emits github.com/LeanerCloud/cloud-commitments-platform/frontend/node_modules/flatted/golang/pkg/flatted. It reaches CI through the pre-commit go-vet hook (go vet ./...), which pre-commit.yml runs after npm ci. The same applies to the pre-push go-test hook and make tidy-check. This PR does not change it, which is why it says Refs #177 rather than Closes. One candidate fix: the Go 1.25+ ignore ./frontend/node_modules directive in go.mod. In the fixture it dropped the package from go list ./... (43 to 42 packages) and would cover every Go-tool ./... walk at once. I didn't make that change here because go.mod is parsed by many tools (Dependabot, gosec, govulncheck, golangci-lint), and checking all of them is more than this xs chore covers. The narrower alternative is go vet $(go list ./... | grep -v /node_modules/) in the hook. If a maintainer prefers either one here, I can add it and switch to Closes.
  • make complexity doesn't exclude _test.go, unlike CI. It fails on main today with 60 test-file findings. I left that alone because changing it would change which files the target checks.
  • The pre-commit hook and CI check slightly different file sets. The git ls-files hook also includes tracked .go files under testdata/, .-prefixed or _-prefixed directories, which CI's walk skips. On main today the two sets are the same.

Labels

Could a maintainer mirror the issue's triage labels onto this PR: effort/xs, impact/internal, priority/p3, severity/low, triaged, type/chore, urgency/eventually.

🤖 Generated with Claude Code

The lint job's complexity check and the Makefile's complexity and
complexity-report targets run `gocyclo ... .`, which walks the whole
checkout. npm packages can ship Go source (frontend/node_modules/flatted
ships flatted.go with Stringify at complexity 32), so any run after
`npm ci` reports third-party functions as findings.

Add `(^|/)(node_modules|vendor)/` to gocyclo's -ignore regex in all three
places. gocyclo v0.6.0 already skips vendor/, testdata/ and dot/underscore
directories, so the vendor/ part only makes that explicit; on a tree
without node_modules the set of scanned files is unchanged.

The pre-commit gocyclo hook is unaffected: it lists files with
`git ls-files`, and frontend/node_modules/ is gitignored.

Refs LeanerCloud#177

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD IAmJSD added impact/internal Team-internal only type/chore Maintenance / non-user-visible triaged Item has been triaged priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/eventually No deadline effort/xs Trivial / one-liner labels Oct 23, 2026
@coderabbitai

coderabbitai Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

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

Labels

effort/xs Trivial / one-liner impact/internal Team-internal only priority/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/chore Maintenance / non-user-visible urgency/eventually No deadline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant