From 13b2604f18687360c8ff7f069941b196350017c1 Mon Sep 17 00:00:00 2001 From: CarterPerez-dev Date: Wed, 13 May 2026 13:28:44 -0400 Subject: [PATCH] fix(canary): replace //nolint pragmas with explicit error handling The standing "no //nolint anywhere" rule (handoff anti-relitigation set) was honored by Phases 2-4 but inherited template code in core/database.go, middleware/ratelimit.go, and health/handler.go carried 6 pragmas from the original template import. Replacing each with proper error handling rather than silencing the linter: core/database.go NewDatabase ping-failure path: propagates db.Close() error via multi-%w fmt.Errorf rather than discarding it via _ = db.Close(). Pattern matches the existing rollback error wrap on line 102. InTx + InTxWithOptions panic-recovery defer: rollback failure now logs via slog.Error before re-panicking, rather than discarding the error via _ = tx.Rollback(). The original error context is preserved by the panic; the rollback failure is observable. jitteredDuration: switches from math/rand/v2 to crypto/rand + math/big. The function runs once per pool init at startup so the per-call cost (~microseconds) is irrelevant. Eliminates the gosec G404 trigger without a global exclude (handoff anti-relitigation: "don't add new excludes for one-off issues"). Adds the missing maxJitter <= 0 guard that the rand.Int64N call would have panicked on with a base smaller than 7ns. middleware/ratelimit.go writeRateLimitExceeded: json.NewEncoder().Encode error is now logged via slog.Error rather than discarded. slog is already imported and used elsewhere in the file (line 60). health/handler.go writeStatus: same pattern; adds log/slog import. Brings the file in line with the rest of the codebase's slog-everywhere discipline. After: zero //nolint pragmas anywhere in backend Go code (grep -rn "//nolint" returns nothing). golangci-lint run ./... reports 0 issues. All unit tests pass under -race; all integration tests pass under -race -tags=integration with the testcontainer Postgres. Surfaced by the pre-Phase-5 cross-phase alignment audit. Cleared as part of finishing the Phase 0 lint-discipline alignment so Phases 5+ inherit a fully consistent codebase. --- .../backend/internal/core/database.go | 42 +++++++++++++++---- .../backend/internal/health/handler.go | 6 ++- .../backend/internal/middleware/ratelimit.go | 5 ++- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/PROJECTS/beginner/canary-token-generator/backend/internal/core/database.go b/PROJECTS/beginner/canary-token-generator/backend/internal/core/database.go index d4849ce3..00d3642b 100644 --- a/PROJECTS/beginner/canary-token-generator/backend/internal/core/database.go +++ b/PROJECTS/beginner/canary-token-generator/backend/internal/core/database.go @@ -5,9 +5,11 @@ package core import ( "context" + crand "crypto/rand" "database/sql" "fmt" - "math/rand/v2" + "log/slog" + "math/big" "time" _ "github.com/jackc/pgx/v5/stdlib" @@ -41,9 +43,15 @@ func NewDatabase( pingCtx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() - if err := db.PingContext(pingCtx); err != nil { - _ = db.Close() //nolint:errcheck // cleanup on connection failure - return nil, fmt.Errorf("ping database: %w", err) + if pingErr := db.PingContext(pingCtx); pingErr != nil { + if closeErr := db.Close(); closeErr != nil { + return nil, fmt.Errorf( + "ping database: %w (close also failed: %w)", + pingErr, + closeErr, + ) + } + return nil, fmt.Errorf("ping database: %w", pingErr) } return &Database{DB: db}, nil @@ -91,7 +99,12 @@ func InTx(ctx context.Context, db *sqlx.DB, fn func(tx *sqlx.Tx) error) error { defer func() { if p := recover(); p != nil { - _ = tx.Rollback() //nolint:errcheck // best-effort rollback on panic + if rbErr := tx.Rollback(); rbErr != nil { + slog.Error( + "rollback failed during panic recovery", + "error", rbErr, + ) + } panic(p) } }() @@ -123,7 +136,12 @@ func InTxWithOptions( defer func() { if p := recover(); p != nil { - _ = tx.Rollback() //nolint:errcheck // best-effort rollback on panic + if rbErr := tx.Rollback(); rbErr != nil { + slog.Error( + "rollback failed during panic recovery", + "error", rbErr, + ) + } panic(p) } }() @@ -143,7 +161,13 @@ func InTxWithOptions( } func jitteredDuration(base time.Duration) time.Duration { - //nolint:gosec // G404: non-security-sensitive jitter for connection pool - jitter := time.Duration(rand.Int64N(int64(base / 7))) - return base + jitter + maxJitter := int64(base / 7) + if maxJitter <= 0 { + return base + } + jitter, err := crand.Int(crand.Reader, big.NewInt(maxJitter)) + if err != nil { + return base + } + return base + time.Duration(jitter.Int64()) } diff --git a/PROJECTS/beginner/canary-token-generator/backend/internal/health/handler.go b/PROJECTS/beginner/canary-token-generator/backend/internal/health/handler.go index e77793c6..c95c2b8f 100644 --- a/PROJECTS/beginner/canary-token-generator/backend/internal/health/handler.go +++ b/PROJECTS/beginner/canary-token-generator/backend/internal/health/handler.go @@ -6,6 +6,7 @@ package health import ( "context" "encoding/json" + "log/slog" "net/http" "sync" "sync/atomic" @@ -174,8 +175,9 @@ func (h *Handler) writeStatus(w http.ResponseWriter, status int, data any) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.WriteHeader(status) - //nolint:errcheck // best-effort response - _ = json.NewEncoder(w).Encode(data) + if err := json.NewEncoder(w).Encode(data); err != nil { + slog.Error("failed to encode health response", "error", err) + } } type StatusResponse struct { diff --git a/PROJECTS/beginner/canary-token-generator/backend/internal/middleware/ratelimit.go b/PROJECTS/beginner/canary-token-generator/backend/internal/middleware/ratelimit.go index c57f38e1..c52b0b0e 100644 --- a/PROJECTS/beginner/canary-token-generator/backend/internal/middleware/ratelimit.go +++ b/PROJECTS/beginner/canary-token-generator/backend/internal/middleware/ratelimit.go @@ -154,8 +154,9 @@ func writeRateLimitExceeded(w http.ResponseWriter, res *redis_rate.Result) { }, } - //nolint:errcheck // best-effort response write - _ = json.NewEncoder(w).Encode(response) + if err := json.NewEncoder(w).Encode(response); err != nil { + slog.Error("failed to encode rate-limit response", "error", err) + } } type limiterEntry struct {