sqlclosecheck reports a false positive when *sqlx.Rows returned by NamedQueryContext is closed inside a defer func() { ... }() block.
golangci-lint version: v2.11.4 (Docker image golangci/golangci-lint:latest-alpine)
Reproducer:
rows, err := r.db.NamedQueryContext(ctx, query, map[string]any{"id": id})
if err != nil {
return nil, err
}
defer func() {
if closeErr := rows.Close(); closeErr != nil {
slog.WarnContext(ctx, "failed to close rows",
"error", closeErr,
)
}
}()
// ... rows.Next(), StructScan, etc.
Linter output:
repository.go:51:37: Rows/Stmt/NamedStmt was not closed (sqlclosecheck)
rows, err := r.db.NamedQueryContext(ctx, query, map[string]any{"id": id})
^
Expected behavior: No error — rows.Close() is called inside the deferred closure, which guarantees cleanup on all return paths.
Observed behavior: sqlclosecheck does not recognize rows.Close() when it appears inside defer func() { ... }() and reports the rows as unclosed.
Notes:
r.db is *sqlx.DB, so NamedQueryContext returns *sqlx.Rows.
- The same code passes with golangci-lint v2.11.3 (sqlclosecheck does not flag it). The regression appeared in v2.11.4.
- Using
defer rows.Close() (without the closure) is recognized correctly, but discards the error from Close().
sqlclosecheckreports a false positive when*sqlx.Rowsreturned byNamedQueryContextis closed inside adefer func() { ... }()block.golangci-lint version: v2.11.4 (Docker image
golangci/golangci-lint:latest-alpine)Reproducer:
Linter output:
Expected behavior: No error —
rows.Close()is called inside the deferred closure, which guarantees cleanup on all return paths.Observed behavior:
sqlclosecheckdoes not recognizerows.Close()when it appears insidedefer func() { ... }()and reports the rows as unclosed.Notes:
r.dbis*sqlx.DB, soNamedQueryContextreturns*sqlx.Rows.defer rows.Close()(without the closure) is recognized correctly, but discards the error fromClose().