Skip to content

fix(lint): properly handle all errcheck violations from golangci-lint v2#1265

Open
cristim wants to merge 2 commits into
mainfrom
fix/main-errcheck-lint
Open

fix(lint): properly handle all errcheck violations from golangci-lint v2#1265
cristim wants to merge 2 commits into
mainfrom
fix/main-errcheck-lint

Conversation

@cristim

@cristim cristim commented Jun 19, 2026

Copy link
Copy Markdown
Member

Problem

golangci-lint v2 activated errcheck.check-blank: true which exposed 107 pre-existing blank-identifier error discards (_ = someCall()). This caused main CI to fail on every lint run.

Approach

The owner decided to properly handle each error, not flip check-blank to false.

Config changes (.golangci.yml)

Three path exclusions for generated/test-helper code where the pattern is unavoidable:

  • internal/mocks/ - testify mock args.Get(0).(*Type) type assertions
  • internal/auth/test_helpers.go
  • internal/database/postgres/testhelpers/

Production code changes

Each of the 16 remaining sites is handled appropriately:

  • Money path (internal/purchase/execution.go): SavePurchaseExecution errors are now logged as Errorf (AUDIT LOSS level); early-exit save extracted to saveExecWithLog helper
  • Scheduler (internal/scheduler/scheduler.go): g.Wait() and GetRecommendations errors are now checked and logged
  • HTTP response writer (internal/server/http.go): w.Write error logged
  • Deferred rollbacks (cmd/cleanup-lambda/main.go, cmd/rekey/main.go, internal/commitmentopts/store_postgres.go): proper rollback error handling with errors.Is(pgx.ErrTxClosed) guard
  • API handler (internal/api/handler.go, various): buildResponse errors propagated or logged; resolveAWSCallerIdentity annotated with //nolint:errcheck (best-effort, STS failure = empty AccountID = security gate)
  • Auth/MFA (internal/auth/service_mfa.go, internal/auth/service_apikeys.go): update errors logged; singleflight .Do annotated with justification
  • Credentials (internal/credentials/cipher.go): DevKey panics on invalid compile-time constant (programming error)
  • Config interactivity (cmd/configure_azure.go, cmd/configure_gcp.go): bufio.Reader.ReadString errors handled via readLine helper; subcommands extracted to keep gocyclo within gate

Complexity helpers extracted (to keep gocyclo gate green)

  • readLine(reader) - bufio.ReadString + io.EOF handling (configure_azure.go)
  • getGCPProjectID(reader) - read + validate GCP project ID (configure_gcp.go)
  • createAzureServicePrincipal(reader, subscriptionID) - SP creation block (configure_azure.go)
  • payloadTypeMatches(payload, want) - type-safe payload type check (internal/api/validation.go)
  • logMigrateVersion(m, steps) - pre-rollback version logging (migrate.go)
  • saveExecWithLog(ctx, exec, accountID) - best-effort save in early-exit path (execution.go)

Test plan

  • go build ./... passes
  • golangci-lint run --enable errcheck reports 0 errcheck violations
  • gocyclo -over 10 $(git ls-files "*.go" | grep -v _test.go | grep -v vendor/) reports 0 violations
  • All pre-commit hooks pass
  • go test ./... passes

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Enhanced error handling throughout the codebase to properly log and report errors that were previously silently ignored
    • Improved transaction rollback error handling in database operations
    • Better CLI input validation with explicit error reporting for configuration commands
    • Strengthened API response validation to catch and log response-building failures
    • Added error detection for credential resolution and service operations
  • Tests

    • Added regression tests for error handling and service failure scenarios

golangci-lint v2 activated errcheck.check-blank:true which exposed 107
pre-existing blank-identifier error discards. This commit handles each
violation properly:

- Exclude generated mock/test-helper paths from errcheck in .golangci.yml
  (internal/mocks/, internal/auth/test_helpers.go,
   internal/database/postgres/testhelpers/)
- Handle errors in production code with log/return as appropriate
- Use //nolint:errcheck with specific justification only for documented
  fire-and-forget cases (singleflight, best-effort STS, w.Write)
- Extract readLine helper (configure_azure.go) and getGCPProjectID,
  logMigrateVersion, createAzureServicePrincipal, payloadTypeMatches
  helpers to keep cyclomatic complexity within the gocyclo <=10 gate
- Save purchase execution errors on money path are now logged as Errorf
- errgroup.Wait + ctx.Err() checked in scheduler fan-out loops
@cristim cristim added triaged Item has been triaged priority/p1 Next up; this sprint type/bug Defect labels Jun 19, 2026
@cristim

cristim commented Jun 19, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@cristim, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 10 minutes and 31 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d4631d93-a3ea-4477-8300-42adec56e2fd

📥 Commits

Reviewing files that changed from the base of the PR and between 2b21899 and 2a20070.

📒 Files selected for processing (5)
  • cmd/configure_azure.go
  • cmd/configure_test.go
  • internal/credentials/resolver.go
  • internal/credentials/resolver_test.go
  • internal/scheduler/scheduler.go
📝 Walkthrough

Walkthrough

This PR is a broad errcheck remediation pass: previously-ignored errors across API handlers, CLI commands, database transactions, services, and providers are now either logged as warnings, propagated to callers, or explicitly annotated with nolint:errcheck. The most substantial functional change is in the Azure recommendations provider, where mergeServiceResults gains an attempted boolean on serviceResult and returns a wrapped error when every attempted service fails.

Changes

Errcheck Cleanup and Azure All-Failed Guard

Layer / File(s) Summary
Lint config exclusions and nolint annotations
.golangci.yml, internal/api/ri_utilization_cache.go, internal/auth/service_apikeys.go, internal/api/handler.go, internal/api/handler_dashboard.go
golangci.yml adds errcheck exclusions for mock and testhelper paths; //nolint:errcheck annotations are added to intentionally-discarded singleflight results and best-effort call sites.
CLI input error handling (Azure and GCP configure)
cmd/configure_azure.go, cmd/configure_gcp.go
Both files introduce a readLine helper with io.EOF-aware error propagation and refactor subscription-ID reading, service-principal creation (createAzureServicePrincipal), project-ID extraction (getGCPProjectID), and command prompts to return errors on read failure.
Transaction rollback error handling
cmd/cleanup-lambda/main.go, cmd/rekey/main.go, internal/commitmentopts/store_postgres.go, internal/database/postgres/migrations/migrate.go
Deferred rollback handlers in deleteExpired, rekeyOne, and PostgresStore.Save change from discarded-error to checked: they log failures and suppress pgx.ErrTxClosed post-commit. A logMigrateVersion helper is extracted for pre-rollback version logging in RollbackMigrations.
API handler and HTTP server errcheck fixes
internal/api/handler.go, internal/api/handler_accounts.go, internal/api/handler_dashboard.go, internal/api/handler_recommendations_refresh.go, internal/api/handler_registrations.go, internal/api/validation.go, internal/server/http.go
buildResponse errors are now propagated or logged in HandleRequest, validateRequest, and validateSecurity; canUseGCPFederated logs HasCredential failures; asyncInvokeSelf returns an error on json.Marshal failure; ClearCollectionStarted failures are logged; a safe payloadTypeMatches helper replaces inline type assertions; w.Write failures are logged in lambdaResponseToHTTP.
Service and infrastructure errcheck fixes
internal/auth/service_mfa.go, internal/credentials/cipher.go, internal/credentials/resolver.go, internal/database/connection.go, internal/purchase/execution.go, internal/scheduler/scheduler.go
MFA service logs UpdateUser errors on expired enrollment cleanup; DevKey() panics on bad devKeyHex; resolveGCPWIFCredential treats LoadRaw errors as absent config; ReleaseAdvisoryLock guards against non-*pgxpool.Conn values; a saveExecWithLog helper centralizes purchase execution persistence logging; scheduler logs errgroup.Wait and GetRecommendations errors.
Azure mergeServiceResults: attempted boolean and all-failed guard
providers/azure/recommendations.go, providers/azure/recommendations_test.go
serviceResult gains an attempted bool; GetRecommendations precomputes include* flags, guards goroutine launch, propagates ctx.Err(), and passes attempted context to mergeServiceResults; mergeServiceResults tracks attempted/failures counters and returns a wrapped error when every attempted service failed; savingsplans/advisor are marked attempted=false; tests cover total failure, partial failure, skipped-service masking, and stable ordering.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • LeanerCloud/CUDly#79: This PR adds a //nolint:errcheck to the resolveAWSCallerIdentity call site in handler.go, directly touching the AWS caller identity resolution error propagation that the referenced PR introduced.
  • LeanerCloud/CUDly#260: Both PRs modify handler_recommendations_refresh.go's async-invoke flow, specifically the MarkCollectionStarted/ClearCollectionStarted lifecycle and marshal/payload error behavior.

Suggested labels

severity/high, urgency/now, impact/internal, effort/s

Poem

🐰 Hopping through the code with care,
Each ignored error laid bare!
Rollbacks logged, panics on bad keys,
Azure's "all failed" guard now sees —
No silent nil slips past my nose,
Every error gets its prose! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(lint): properly handle all errcheck violations from golangci-lint v2' clearly and specifically describes the main change: addressing errcheck linting violations from golangci-lint v2 by properly handling error checks across the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 96.67% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/main-errcheck-lint

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

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
cmd/configure_gcp.go (1)

388-389: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid re-creating stdin readers inside command execution.

Line [415] creates a fresh bufio.Reader(os.Stdin) rather than reusing the caller’s reader. In piped/scripted flows this can lose buffered bytes and stall follow-up prompts.

Suggested fix
 func promptAndRunGCPCommand(reader *bufio.Reader, name, displayCmd string, program string, args ...string) error {
@@
 	switch choice {
 	case "r", "run", "":
-		return executeGCPCommand(displayCmd, program, args...)
+		return executeGCPCommand(reader, displayCmd, program, args...)
@@
-func executeGCPCommand(displayCmd string, program string, args ...string) error {
+func executeGCPCommand(reader *bufio.Reader, displayCmd string, program string, args ...string) error {
@@
 	if err != nil {
 		fmt.Printf("Command failed: %v\n", err)
 		fmt.Print("Continue anyway? [y/N]: ")
-		reader := bufio.NewReader(os.Stdin)
 		response, rfErr := readLine(reader)

Also applies to: 415-418

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/configure_gcp.go` around lines 388 - 389, The code is creating a fresh
bufio.Reader from os.Stdin instead of reusing the reader parameter passed to the
function, which causes loss of buffered bytes in piped/scripted flows. Replace
the bufio.Reader(os.Stdin) instantiation (around line 415) with the existing
reader parameter that was passed to the current function, ensuring the same
reader instance is reused throughout the command execution flow to preserve any
buffered input.
cmd/configure_azure.go (1)

386-387: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reuse the same buffered reader for follow-up prompts.

Line [413] creates a new bufio.Reader(os.Stdin) inside executeExplicitCommand. If input is piped and the caller’s reader already buffered bytes, this can drop buffered input and block/hang on retries.

Suggested fix
 func promptAndRunExplicitCommand(reader *bufio.Reader, name, displayCmd string, program string, args ...string) error {
@@
 	switch choice {
 	case "r", "run", "":
-		return executeExplicitCommand(displayCmd, program, args...)
+		return executeExplicitCommand(reader, displayCmd, program, args...)
@@
-func executeExplicitCommand(displayCmd string, program string, args ...string) error {
+func executeExplicitCommand(reader *bufio.Reader, displayCmd string, program string, args ...string) error {
@@
 	if err != nil {
 		fmt.Printf("Command failed: %v\n", err)
 		fmt.Print("Continue anyway? [y/N]: ")
-		reader := bufio.NewReader(os.Stdin)
 		response, readErr := readLine(reader)

Also applies to: 413-417

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/configure_azure.go` around lines 386 - 387, The executeExplicitCommand
function creates a new bufio.Reader(os.Stdin) at lines 413-417, which drops any
buffered bytes from the caller's existing reader and causes input loss when
piped. Modify executeExplicitCommand to accept a *bufio.Reader parameter instead
of creating a new one internally. Then update the function call at lines 386-387
(and any other calls to executeExplicitCommand) to pass the caller's existing
buffered reader as an argument, ensuring the same reader instance is reused for
all prompts and input operations.
providers/azure/recommendations.go (1)

167-167: ⚠️ Potential issue | 🟠 Major

Handle g.Wait() error instead of blank-discarding it.

With check-blank: true enabled in .golangci.yml, blank-discarding the error from g.Wait() at line 167 violates lint rules and masks potential failures. Add proper error handling:

Proposed fix
-	_ = g.Wait()
+	if err := g.Wait(); err != nil {
+		return nil, err
+	}
 	if err := ctx.Err(); err != nil {
 		return nil, err
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@providers/azure/recommendations.go` at line 167, The g.Wait() error is being
blank-discarded with the underscore assignment which violates the golangci-yml
check-blank rule and hides potential failures. Replace the `_ = g.Wait()`
statement with proper error handling by capturing the returned error, checking
if it is not nil, and then either returning the error to the caller, logging it
appropriately, or handling it based on the context of the recommendations.go
function logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/configure_azure.go`:
- Around line 261-266: In the readLine function, the issue is that when io.EOF
is encountered (indicating closed or non-interactive stdin), the error is being
silently converted to nil even when no data was read. This allows empty input
from closed stdin to be treated as a valid empty choice. Fix this by checking
whether the trimmed line is actually empty when io.EOF is encountered. If the
line is empty and we received an EOF error, return the EOF error instead of nil
to properly indicate that no valid input was provided. Only convert EOF to nil
success when actual data was successfully read before the EOF.

In `@internal/credentials/resolver.go`:
- Around line 411-416: The current code treats all LoadRaw errors uniformly by
setting raw to nil, but according to the CredentialStore interface, only
not-found cases should fall back to the federated path while other errors
represent real store failures that need to be surfaced. Modify the error
handling for the store.LoadRaw call to use errors.Is(loadErr, pgx.ErrNoRows) to
specifically check if the error is a not-found error, and only set raw = nil in
that case. For other non-nil errors, propagate or return the error instead of
silently collapsing it to nil.

In `@internal/scheduler/scheduler.go`:
- Around line 352-355: In the collectAllProviders function, the error returned
from g.Wait() is being logged but not propagated, which allows the function to
continue and return success even when goroutines have failed. Instead of only
logging the waitErr, check if waitErr is not nil and return it immediately to
ensure that any goroutine errors are properly surfaced to the caller and prevent
partial/incorrect results from being returned.

---

Outside diff comments:
In `@cmd/configure_azure.go`:
- Around line 386-387: The executeExplicitCommand function creates a new
bufio.Reader(os.Stdin) at lines 413-417, which drops any buffered bytes from the
caller's existing reader and causes input loss when piped. Modify
executeExplicitCommand to accept a *bufio.Reader parameter instead of creating a
new one internally. Then update the function call at lines 386-387 (and any
other calls to executeExplicitCommand) to pass the caller's existing buffered
reader as an argument, ensuring the same reader instance is reused for all
prompts and input operations.

In `@cmd/configure_gcp.go`:
- Around line 388-389: The code is creating a fresh bufio.Reader from os.Stdin
instead of reusing the reader parameter passed to the function, which causes
loss of buffered bytes in piped/scripted flows. Replace the
bufio.Reader(os.Stdin) instantiation (around line 415) with the existing reader
parameter that was passed to the current function, ensuring the same reader
instance is reused throughout the command execution flow to preserve any
buffered input.

In `@providers/azure/recommendations.go`:
- Line 167: The g.Wait() error is being blank-discarded with the underscore
assignment which violates the golangci-yml check-blank rule and hides potential
failures. Replace the `_ = g.Wait()` statement with proper error handling by
capturing the returned error, checking if it is not nil, and then either
returning the error to the caller, logging it appropriately, or handling it
based on the context of the recommendations.go function logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3a5dd19b-1687-434c-a0a1-ceb2589cf9eb

📥 Commits

Reviewing files that changed from the base of the PR and between 451a70f and 2b21899.

📒 Files selected for processing (24)
  • .golangci.yml
  • cmd/cleanup-lambda/main.go
  • cmd/configure_azure.go
  • cmd/configure_gcp.go
  • cmd/rekey/main.go
  • internal/api/handler.go
  • internal/api/handler_accounts.go
  • internal/api/handler_dashboard.go
  • internal/api/handler_recommendations_refresh.go
  • internal/api/handler_registrations.go
  • internal/api/ri_utilization_cache.go
  • internal/api/validation.go
  • internal/auth/service_apikeys.go
  • internal/auth/service_mfa.go
  • internal/commitmentopts/store_postgres.go
  • internal/credentials/cipher.go
  • internal/credentials/resolver.go
  • internal/database/connection.go
  • internal/database/postgres/migrations/migrate.go
  • internal/purchase/execution.go
  • internal/scheduler/scheduler.go
  • internal/server/http.go
  • providers/azure/recommendations.go
  • providers/azure/recommendations_test.go

Comment thread cmd/configure_azure.go Outdated
Comment thread internal/credentials/resolver.go
Comment thread internal/scheduler/scheduler.go
Three Major CR findings on the errcheck changes, each fixed at root cause
rather than silencing the linter:

- cmd/configure_azure.go: readLine treated a zero-byte io.EOF as a
  successful empty line, so a closed/non-interactive stdin looked like an
  empty choice and fell through to the default Run in prompt handlers. Now
  EOF is success only when bytes were actually read (final line without a
  trailing newline); an EOF with no data surfaces the error. Uses the raw
  read buffer (not the trimmed text) so a bare newline still counts as a
  valid empty line rather than a closed stream.

- internal/credentials/resolver.go: resolveGCPWIFCredential swallowed every
  LoadRaw error by setting raw=nil and falling back to the federated path.
  LoadRaw already maps not-found to (nil, nil), so any non-nil error is a
  real store failure (DB connectivity, decrypt) that must be surfaced.
  Matches the established pattern in resolveAccessKeyProvider.

- internal/scheduler/scheduler.go: collectAllProviders logged g.Wait()'s
  error but still merged outcomes, risking a partial aggregate reported as
  success. It now returns the error. fanOutPerAccount has no error return,
  so an unexpected g.Wait() error is reflected into the accountOutcome
  (FailedCount/LastErr) instead of being silently swallowed.

Regression tests added: TestReadLine_EOFHandling (table-driven, covers the
closed-stream-surfaces-EOF and bare-newline cases) and
TestResolveGCPWIF_LoadRawError_Surfaces (wires the full federated path so
the pre-fix code would mask the store error). Both fail on the pre-fix code
and pass after.
@cristim

cristim commented Jun 19, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Labels

priority/p1 Next up; this sprint triaged Item has been triaged type/bug Defect

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant