Skip to content

ci: go vet/lint steps don't use the per-module loop convention [cli part] #1478

Description

@cristim

Summary

ci.yml's three core code-health checks silently skip every module except the root one, in a repo that has been multi-module (go.work: ., pkg, providers/aws, providers/azure, providers/gcp, tests/e2e) for some time:

  • "Run go vet" (.github/workflows/ci.yml around line 53-54): go vet ./...
  • "Run unit tests" (Unit Tests job, around line 97-99): go test -v -race -short -coverprofile=coverage.out -covermode=atomic ./...
  • "Run integration tests" (Integration Tests job, around line 180): go test -v -race -tags=integration -coverprofile=coverage-integration.out ./...
  • Lint Code job: golangci-lint run --timeout=10m (no path arg)

In a Go workspace, a bare ./... from the repo root only expands to packages in the current module (confirmed locally: go list ./... from repo root returns 38 packages, zero of which are under providers/). go.work only affects cross-module dependency resolution, not what ./... expands to.

The govulncheck and gosec steps already handle this correctly — both loop for mod in . pkg providers/aws providers/azure providers/gcp tests/e2e; do (cd "$mod" && <tool> ./...); done, with an inline comment explaining exactly why. The same fix was never applied to go vet, unit tests, integration tests, or golangci-lint.

Impact

  • providers/aws, providers/azure, providers/gcp, pkg, and tests/e2e get zero go vet/lint coverage and their Go tests never run in CI, even though these packages contain the actual cloud-provider purchase logic (the highest money-risk code in the repo).
  • Confirmed while verifying PR fixing the EC2 RI term silent-default bug (follow-up to fix(providers/aws): fail loud on unrecognized RI term strings #1207): the fix and its new regression tests live in providers/aws/services/ec2, so go build/vet/test and lint for that package are exercised only by whatever a contributor runs locally, not by CI.
  • Locally reproducing golangci-lint v2.10.1 (the exact CI-pinned version) scoped to providers/aws/services/ec2 alone (not ./... bare) surfaces ~27 pre-existing findings (misspellings, gocritic rangeValCopy, a gosec G117 field-name match, godot comment-period nits) that have presumably been accumulating silently since this gap opened.

Suggested fix

Mirror the govulncheck/gosec per-module loop for the go vet, unit-test, integration-test, and golangci-lint steps. Since this will likely surface the accumulated per-module lint/vet debt (~27+ in providers/aws alone, azure/gcp not yet surveyed), budget this as a two-part effort: (1) wire up the per-module loop, (2) batch-fix the debt it uncovers rather than suppressing it (per this repo's "no masking CI debt" convention) or gating it behind continue-on-error.

Follow-up

Filed while adversarially verifying a merged PR; not fixed here to keep that fix scoped. Assessing severity as high (money-risk provider code has zero CI vet/test/lint coverage) but not critical (no active incident; the gap is latent, not currently causing production harm), priority p1 (should be next up, not urgent-drop-everything), effort m (loop change is small; the debt cleanup it uncovers is the real size unknown until surveyed).

Scope after the split

The CUDly monorepo was split into four repos. The providers/aws, providers/azure, providers/gcp, and pkg modules named above now live in cloud-commitments-go; that repo has the genuine live version of this gap (go vet and lint still bare, still skipping five of its six go.work modules) — see LeanerCloud/cloud-commitments-go#118. cloud-commitments-platform inherited the tests/e2e module and has the same live gap for it — see LeanerCloud/cloud-commitments-platform#346.

cloud-commitments-cli itself is a single-module repo (go.work only declares use .): there is no second module for go vet/lint to silently skip here today, and the unit-test/integration-test steps already loop for mod in .. This issue is kept open, scoped down to a consistency/hardening item: go vet and the lint action are the only two CI checks in this repo not using that same loop convention, which would silently regress into this exact bug if a second module is ever added.

Sibling issues:

Acceptance criteria (this repo)

  • go vet and the golangci-lint invocation are rewritten to use the same for mod in . loop pattern as the unit-test/integration-test/govulncheck/gosec steps, for consistency and to guard against a silent regression if a second module is added later.
  • No behavioral change expected today (single module); confirm go vet/lint output is unchanged before and after.

Activity

  1. cristim commented on Jul 28, 2026

    @cristim
    MemberAuthor

    Reviewed commit: be11bdcb5. Note: origin/main moved to 3e9660d06 during the review; re-verify against current main before changing code, since a finding may have been fixed or moved.

    The 2026-07-28 full review re-derived this independently and reached the same conclusion, so this is confirmation rather than a new issue. Adding the quantification and three details the original write-up does not have, plus a recommendation to raise the severity.

    Where (line numbers from 3e9660d06)

    • .github/workflows/ci.yml:99 (go test -race -short ./...) and :189 (go test -tags=integration ./...), both from the repo root
    • .github/workflows/ci.yml:48 (golangci-lint, runs in repo root), :54 (go vet ./...), :94 (go mod download && go mod verify)
    • go.work:3-10; Makefile:65,70
    • .pre-commit-config.yaml:19 (go-vet hook, root only), :187 (go-test hook is pre-push-stage only, so it never runs in CI)
    • The correct pattern already in the same file: ci.yml:312 (govulncheck per-module loop), :333 (gosec per-module loop)

    Quantification

    Counted with git ls-tree origin/main plus git grep -c '^func Test':

    Module packages .go files _test.go files func Test* never executed
    pkg 11 61 30 351
    providers/aws 12 65 31 518
    providers/azure 14 38 17 516
    providers/gcp 5 15 8 184
    total 42 179 (~30% of the repo's Go source) 86 1,569

    Three details not in the original issue

    1. Production code in these modules is still compiled. go list -deps ./... from the root reaches 30 providers/* packages and 10 pkg/* packages through the replace directives at go.mod:191-197. So compile errors are caught today. What is never run is go vet (vet analyses only named packages, not their dependencies), golangci-lint, and every one of the 1,569 test functions. Scoping the fix around this matters: the change is "run the existing tests", not "make these modules build".

    2. .golangci.yml:134 lists pkg/ladder/... in a misspell exclusion path. Someone wrote a lint exclusion for a module that lint has never visited. That is direct evidence the gap is invisible to authors, not merely unaddressed.

    3. The per-module loop pattern already exists in the same file. ci.yml:308-310 documents this exact ./... behaviour for govulncheck, and ci.yml:333 loops all six modules for gosec. So the fix is applying an established, already-reviewed in-repo pattern to three more jobs, not a redesign. That should lower the perceived cost of doing it.

    Failure scenario (why this warrants a severity bump)

    These are the money paths. providers/aws/services/{ec2,rds,savingsplans,...} and providers/azure/services/compute execute reservation and savings-plan purchases. A regression in reservation-term handling, idempotency-token scoping, or price math lands with a fully green CI, because the 518 AWS-provider and 516 Azure-provider tests that would have caught it are never invoked. This is precisely the "green tests are NOT proof the feature works" failure mode the repo has already been bitten by; here it is structural rather than incidental, since the tests do not merely fail to cover the path, they do not run at all.

    Suggest raising this from severity/high to severity/critical and keeping priority/p1: it is the single largest gap in the project's verification story, and several other findings in this review are only findable by review because CI cannot see them.

    Fix direction (unchanged from the original, with one addition)

    Replicate the for mod in . pkg providers/aws providers/azure providers/gcp tests/e2e loop from ci.yml:312 into the unit-tests, integration-tests and lint jobs. golangci-lint needs working-directory per module, because the action does not honour go.work. Merge the per-module coverage profiles before the Codecov upload so the coverage number stops describing only the root module.

  2. changed the title [-]ci: go vet/unit-tests/integration-tests/lint only cover root module, silently skip providers/*, pkg, tests/e2e[/-] [+]ci: go vet/lint steps don't use the per-module loop convention [cli part][/+] on Sep 27, 2026
  3. cristim commented on Sep 27, 2026

    @cristim
    MemberAuthor

    This is a cross-component issue from before the CUDly monorepo split into four repos. The providers/*/pkg modules it originally referenced now live in cloud-commitments-go, which has the genuine live version of this gap; tests/e2e now lives in cloud-commitments-platform, with the same live gap. This repo (cloud-commitments-cli) is single-module, so the bug doesn't currently reproduce here; kept open as a smaller consistency/hardening item.

  4. cristim commented on Sep 30, 2026

    @cristim
    MemberAuthor

    Merged and locally verified: PR #2126, merge commit 93b9b6db5773a999cd55a06e2b2458853dea2ff4.

    The complete merged tree is identical to independently reviewed commit 1321c11f7af6fd02a67e67bf28dab50c69c5256a (both tree IDs 4390ac73ec5d30e326fc6a4df8e2bf1471ae948f). Checked out the exact merge commit in the preserved local clone for fresh verification.

    Fresh macOS verification with Go 1.26.6 / golangci-lint 2.10.1: parsed and executed the actual merged YAML lint/vet blocks; both passed, and stdout/stderr match the pre-change baseline after removing only the new module banners. Native actionlint 1.7.12 passed. Five supplemental shell fixtures passed, including configuration failure preventing lint and lint/vet failure propagation.

    This meets the post-split consistency criteria. The CLI remains one module; no new coverage or automatic module discovery is claimed. Pre-merge race-short tests, build and normal hooks also passed; they were not redundantly rerun for this identical tree. Main CI runs 36653580722 and 36653580751 remain under root monitoring; no main-CI result is claimed here.

    Recommendation: the issue can remain closed. Follow-up audit found no separate actionable defect; no follow-up issues filed. Clone and branch preserved.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions