Skip to content

fprintferrorunchecked (76th linter): AssignStmt-only filter misses 938 bare ExprStmt call sites #65225

Description

@github-actions

Linter: fprintferrorunchecked (76th registered analyzer, PR #65073, added 2026-10-02, first-ever audit)

Problem

The entire analysis is gated by a single nodeFilter entry:

nodeFilter := []ast.Node{(*ast.AssignStmt)(nil)}

(fprintferrorunchecked.go:40). checkUncheckedFprintAssign only recognizes the explicit-blank-assignment shapes _ = fmt.Fprintf(...) and _, _ = fmt.Fprintf(...). A bare statement call with no assignment at all, e.g. fmt.Fprintf(w, "text"), is legal Go that silently discards both return values -- exactly the write-failure-ignored case this linter exists to catch -- yet it never reaches checkUncheckedFprintAssign because there is no AssignStmt node for it.

Evidence

Grep across pkg/ (excluding _test.go) for the bare-statement shape ^\tfmt\.(Fprintf|Fprintln|Fprint)\( returned 938 matches across 193 files -- vastly more common than the explicit _, _ = form this linter actually detects. Two concrete production sites:

  • pkg/console/confirm.go:53-56 -- fmt.Fprintf(out, ...) writing to an io.Writer parameter (e.g. os.Stdout in real callers), with zero error handling. This is precisely the silently-ignored-write-failure scenario described in the linters own diagnostic message.
  • pkg/workflow/workflow_errors.go:99 -- fmt.Fprintf(&b, ...) building an error string via bytes.Buffer.

CI enforcement

.github/workflows/cgo.yml LINTER_FLAGS already include -fprintferrorunchecked on both the native (line 1487) and wasm (line 1490) custom-linter steps with -test=false, so this gate is active in CI today while being structurally blind to the overwhelming majority of real Fprint* call sites in the codebase.

Pattern class: node_filter_too_narrow (same class previously found in globwalkignorederror/strconvparseignorederror, issues #61265/#63094), but at far larger scale here -- 938 live occurrences versus a handful in prior instances of this class.

Recommendation

Add an *ast.ExprStmt case alongside the existing *ast.AssignStmt case in run() (fprintferrorunchecked.go:40-50): unwrap the ExprStmt to its *ast.CallExpr, then reuse the existing extractFunctionName / isFprintFunction / isFprintCallReturningError / reportUncheckedFprint helpers unchanged.

Validation checklist

  • Add a testdata case for the bare-statement form, e.g. fmt.Fprintf(w, "text") as a standalone ExprStmt, expecting a diagnostic
  • Confirm existing AssignStmt-based good/bad cases still pass unchanged
  • Run make golint-custom LINTER_FLAGS="-fprintferrorunchecked -test=false" against pkg/console and pkg/workflow to confirm the fix surfaces new findings without introducing false positives on checked calls

Effort: small -- one new ExprStmt branch reusing all existing helper functions.

Generated by 🤖 Sergo - Serena Go Expert · claude · agent · 317.8 AIC · ⌖ 7.26 AIC · ⊞ 5.3K · ◷

  • expires on Oct 9, 2026, 8:11 PM UTC-08:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    cookieIssue Monster Loves Cookies!sergo

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions