Skip to content

[aw] PR Code Quality Reviewer failed #49868

Description

@github-actions

Workflow Failure

Workflow: PR Code Quality Reviewer
Branch: copilot/aw-fix-linter-miner-failure
Run: https://github.com/github/gh-aw/actions/runs/30770509772
Pull Request: #49862

Warning

Engine Failure: The copilot engine terminated unexpectedly.

Last agent output:

{"type":"tool.execution_complete","timestamp":"2026-08-02T22:43:44.730Z","data":{"toolName":"bash","mcpServerName":"","success":true,"result":{"content":"{\"additions\":215,\"body\":\"The Linter Miner workflow identified `Flush()` return-value discarding as an undetected error class — `errcheck` is disabled repo-wide and no existing linter covered it. Two live violations existed in `pkg/cli/logs_format_compact.go`.\\n\\n## New analyzer: `uncheckedflushreturn`\\n\\nFlags `Flush()` method calls where the `error` return is silently discarded. Uses type info to confirm the method signature is `func() error`, avoiding false positives on `Flush` methods that don't return errors.\\n\\nDetected patterns:\\n```go\\ntw.Flush()       // bare expression — flagged\\n_ = tw.Flush()   // explicit discard — flagged\\n\\nif err := tw.Flush(); err != nil { ... }  // ok\\n```\\n\\n## Fixes\\n\\n- **`pkg/cli/logs_format_compact.go`** — two `tabwriter.Writer.Flush()` calls now check and log the error via the package logger.\\n\\n## Registration\\n\\n- Added to `pkg/linters/registry.go` (`allAnalyzers`)\\n- `doc.go` count updated 61 → 62, bullet added\\n- `README.md` bullet list, subpackages table, and import list updated\\n- `spec_test.go` import, `documentedAnalyzers()` entry, and count comment updated\\n\\n<!-- START COPILOT CODING AGENT SUFFIX -->\\n\\n- Fixes #49821\",\"changedFiles\":9,\"deletions\":5,\"files\":[{\"path\":\"linters\",\"additions\":0,\"deletions\":0,\"changeType\":\"MODIFIED\"},{\"path\":\"pkg/cli/logs_format_compact.go\",\"additions\":6,\"deletions\":2,\"changeType\":\"MODIFIED\"},{\"path\":\"pkg/linters/README.md\",\"additions\":3,\"deletions\":0,\"changeType\":\"MODIFIED\"},{\"path\":\"pkg/linters/doc.go\",\"additions\":2,\"deletions\":1,\"changeType\":\"MODIFIED\"},{\"path\":\"pkg/linters/registry.go\",\"additions\":2,\"deletions\":0,\"changeType\":\"MODIFIED\"},{\"path\":\"pkg/linters/spec_test.go\",\"additions\":4,\"deletions\":2,\"changeType\":\"MODIFIED\"},{\"path\":\"pkg/linters/uncheckedflushreturn/testdata/src/uncheckedflushreturn/uncheckedflushreturn.go\",\"additions\":46,\"deletions\":0,\"changeType\":\"ADDED\"},{\"path\":\"pkg/linters/uncheckedflushreturn/uncheckedflushreturn.go\",\"additions\":136,\"deletions\":0,\"changeType\":\"ADDED\"},{\"path\":\"pkg/linters/uncheckedflushreturn/uncheckedflushreturn_test.go\",\"additions\":16,\"deletions\":0,\"changeType\":\"ADDED\"}],\"headRefName\":\"copilot/aw-fix-linter-miner-failure\",\"headRefOid\":\"68fbfeeb4d24fe0bb062888787e97f381c4f476a\",\"number\":49862,\"title\":\"feat(linters): add uncheckedflushreturn analyzer + fix existing violations\"}\n---\n344 /tmp/gh-aw/agent/pr-diff.patch\n---\n[]\n<shellId: 10 completed with exit code 0>","detailedContent":"{\"additions\":215,\"body\":\"The Linter Miner workflow identified `Flush()` return-value discarding as an undetected error class — `errcheck` is disabled repo-wide and no existing linter covered it. Two live violations existed in `pkg/cli/logs_format_compact.go`.\\n\\n## New analyzer: `uncheckedflushreturn`\\n\\nFlags `Flush()` method calls where the `error` return is silently discarded. Uses type info to confirm the method signature is `func() error`, avoiding false positives on `Flush` methods that don't return errors.\\n\\nDetected patterns:\\n```go\\ntw.Flush()       // bare expression — flagged\\n_ = tw.Flush()   // explicit discard — flagged\\n\\nif err := tw.Flush(); err != nil { ... }  // ok\\n```\\n\\n## Fixes\\n\\n- **`pkg/cli/logs_format_compact.go`** — two `tabwriter.Writer.Flush()` calls now check and log the error via the package logger.\\n\\n## Registration\\n\\n- Added to `pkg/linters/registry.go` (`allAnalyzers`)\\n- `doc.go` count updated 61 → 62, bullet added\\n- `README.md` bullet list, subpackages table, and import list updated\\n- `spec_test.go` import, `documentedAnalyzers()` entry, and count comment updated\\n\\n<!-- START COPILOT CODING AGENT SUFFIX -->\\n\\n- Fixes #49821\",\"changedFiles\":9,\"deletions\":5,\"files\":[{\"path\":\"linters\",\"additions\":0,\"deletions\":0,\"changeType\":\"MODIFIED\"},{\"path\":\"pkg/cli/logs_format_compact.go\",\"additions\":6,\"deletions\":2,\"changeType\":\"MODIFIED\"},{\"path\":\"pkg/linters/README.md\",\"additions\":3,\"deletions\":0,\"changeType\":\"MODIFIED\"},{\"path\":\"pkg/linters/doc.go\",\"additions\":2,\"deletions\":1,\"changeType\":\"MODIFIED\"},{\"path\":\"pkg/linters/registry.go\",\"additions\":2,\"deletions\":0,\"changeType\":\"MODIFIED\"},{\"path\":\"pkg/linters/spec_test.go\",\"additions\":4,\"deletions\":2,\"changeType\":\"MODIFIED\"},{\"path\":\"pkg/linters/uncheckedflushreturn/testdata/src/uncheckedflushreturn/uncheckedflushreturn.go\",\"additions\":46,\"deletions\":0,\"changeType\":\"ADDED\"},{\"path\":\"pkg/linters/uncheckedflushreturn/uncheckedflushreturn.go\",\"additions\":136,\"deletions\":0,\"changeType\":\"ADDED\"},{\"path\":\"pkg/linters/uncheckedflushreturn/uncheckedflushreturn_test.go\",\"additions\":16,\"deletions\":0,\"changeType\":\"ADDED\"}],\"headRefName\":\"copilot/aw-fix-linter-miner-failure\",\"headRefOid\":\"68fbfeeb4d24fe0bb062888787e97f381c4f476a\",\"number\":49862,\"title\":\"feat(linters): add uncheckedflushreturn analyzer + fix existing violations\"}\n---\n344 /tmp/gh-aw/agent/pr-diff.patch\n---\n[]\n<shellId: 10 completed with exit code 0>"}}}
{"type":"assistant.message","timestamp":"2026-08-02T22:43:46.750Z","data":{"content":""}}
{"type":"tool.execution_start","timestamp":"2026-08-02T22:43:46.752Z","data":{"toolName":"bash","mcpServerName":"","command":"cat /tmp/gh-aw/agent/pr-diff.patch"}}
{"type":"tool.execution_complete","timestamp":"2026-08-02T22:43:46.825Z","data":{"toolName":"bash","mcpServerName":"","success":true,"result":{"content":"diff --git a/linters b/linters\nindex d920e6a6151..5725ab9455a 100755\nBinary files a/linters and b/linters differ\ndiff --git a/pkg/cli/logs_format_compact.go b/pkg/cli/logs_format_compact.go\nindex ae0dac8f09d..05ba00a8012 100644\n--- a/pkg/cli/logs_format_compact.go\n+++ b/pkg/cli/logs_format_compact.go\n@@ -137,7 +137,9 @@ func renderLogsCompactToWriter(w io.Writer, data LogsData) {\n \t\t\tr.TokenUsage, formatCompactAIC(r.AIC), r.Turns, r.ErrorCount,\n \t\t\tr.Event, actor, branch)\n \t}\n-\ttw.Flush()\n+\tif err := tw.Flush(); err != nil {\n+\t\tlogsCompactLog.Printf(\"flush error: %v\", err)\n+\t}\n \n \t// [errors] — aggregated error/warning messages\n \tif len(data.ErrorsAndWarnings) > 0 {\n@@ -317,7 +319,9 @@ func renderLogsCompactVerboseToWriter(w io.Writer, data LogsData) {\n \t\t\tr.Event, actor, tbt, classification,\n \t\t\tr.CreatedAt.Format(\"01-02 15:04\"), r.Branch)\n \t}\n-\ttw.Flush()\n+\tif err := tw.Flush(); err != nil {\n+\t\tlogsCompactLog.Printf(\"flush error: %v\", err)\n+\t}\n \n \t// [errors]\n \tif len(data.ErrorsAndWarnings) > 0 {diff --git a/pkg/linters/README.md b/pkg/linters/README.md\nindex 6ee1ea8b056..836eb89dfdb 100644\n--- a/pkg/linters/README.md\n+++ b/pkg/linters/README.md\n@@ -64,6 +64,7 @@ This package currently provides custom Go analyzers in the following subpackages\n - `tolowerequalfold` — reports case-insensitive string comparisons using `strings.ToLower`/`ToUpper` that should use `strings.EqualFold`.\n - `trimleftright` — reports `strings.TrimLeft`/`TrimRight` calls with a multi-character literal cutset where `TrimPrefix`/`TrimSuffix` was likely intended.\n - `uncheckedtypeassertion` — reports single-value type assertions where unchecked panics are possible.\n+- `uncheckedflushreturn` — reports `Flush()` method calls where the error return is discarded, which silently drops buffered data on failure.\n - `wgdonenotdeferred` — reports non-deferred `sync.WaitGroup.Done()` calls that can deadlock on panics or early returns.\n - `writebytestring` — reports `w.Write([]byte(s))` calls where `s` is a string, which can be replaced with `io.WriteString` to avoid an unnecessary `[]byte` allocation.\n - `internal` — shared helper packages for analyzers (file checks and `nolint` handling).\n@@ -132,6 +133,7 @@ This package currently provides custom Go analyzers in the following subpackages\n | `tolowerequalfold` | Custom `go/analysis` analyzer that flags case-insensitive comparisons via `strings.ToLower`/`ToUpper` that should use `strings.EqualFold` |\n | `trimleftright` | Custom `go/analysis` analyzer that flags `strings.TrimLeft`/`TrimRight` calls with a multi-character literal cutset where `TrimPrefix`/`TrimSuffix` was likely intended |\n | `uncheckedtypeassertion` | Custom `go/analysis` analyzer that flags unchecked single-value type assertions |\n+| `uncheckedflushreturn` | Custom `go/analysis` analyzer that flags `Flush()` method calls where the error return is discarded |\n | `walkfuncerrshadow` | Custom `go/analysis` analyzer that flags `filepath.Walk`/`filepath.WalkDir` callbacks whose `err` parameter shadows an outer `err` variable assigned from the walk call |\n | `wgdonenotdeferred` | Custom `go/analysis` analyzer that flags non-deferred `sync.WaitGroup.Done()` calls |\n | `writebytestring` | Custom `go/analysis` analyzer that flags `w.Write([]byte(s))` calls where `s` is a string that can be replaced with `io.WriteString` |\n@@ -262,6 +264,7 @@ _ = trimleftright.Analyzer\n - `github.com/github/gh-aw/pkg/linters/tolowerequalfold` — to-lower-equal-fold analyzer subpackage\n - `github.com/github/gh-aw/pkg/linters/trimleftright` — trim-left-right analyzer subpackage\n - `github.com/github/gh-aw/pkg/linters/uncheckedtypeassertion` — unchecked-type-assertion analyzer subpackage\n+- `github.com/github/gh-aw/pkg/linters/uncheckedflushreturn` — unchecked-flush-return analyzer subpackage\n - `github.com/github/gh-aw/pkg/linters/walkfuncerrshadow` — walk-func-err-shadow analyzer subpackage\n - `github.com/github/gh-aw/pkg/linters/wgdonenotdeferred` — wg-done-not-deferred analyzer subpackage\n - `github.com/github/gh-aw/pkg/linters/writebytestring` — write-byte-string analyzer subpackagediff --git a/pkg/linters/doc.go b/pkg/linters/doc.go\nindex 53ef264705b..3a0f83c8de4 100644\n--- a/pkg/linters/doc.go\n+++ b/pkg/linters/doc.go\n@@ -1,6 +1,6 @@\n // Package linters is a namespace for gh-aw's custom Go analysis linters.\n //\n-// All 61 active analyzers:\n+// All 62 active analyzers:\n //\n //   - appendbytestring — flags append(b, []byte(s)...) calls where s is a string that can be simplified to append(b, s...)\n //   - appendoneelement — flags append(s, []T{x}...) calls where a single-element slice literal is spread and can be simplified to append(s, x)\n@@ -60,6 +60,7 @@\n //   - tolowerequalfold — flags case-insensitive comparisons via ToLower/ToUpper that should use EqualFold\n //   - trimleftright — flags strings.TrimLeft/TrimRight calls with a multi-character literal cutset where TrimPrefix/TrimSuffix was likely intended\n //   - uncheckedtypeassertion — flags unchecked single-value type assertions\n+//   - uncheckedflushreturn — flags Flush() method calls where the error return is discarded\n //   - walkfuncerrshadow — flags filepath.Walk/WalkDir callbacks whose err parameter shadows an outer err variable assigned from the walk call\n //   - wgdonenotdeferred — flags non-deferred sync.WaitGroup.Done() calls\n //   - writebytestring — flags w.Write([]byte(s)) calls where s is a string that can be replaced with io.WriteString(w, s)diff --git a/pkg/linters/registry.go b/pkg/linters/registry.go\nindex f20b15e227d..af0285ac238 100644\n--- a/pkg/linters/registry.go\n+++ b/pkg/linters/registry.go\n@@ -60,6 +60,7 @@ import (\n \t\"github.com/github/gh-aw/pkg/linters/timesleepnocontext\"\n \t\"github.com/github/gh-aw/pkg/linters/tolowerequalfold\"\n \t\"github.com/github/gh-aw/pkg/linters/trimleftright\"\n+\t\"github.com/github/gh-aw/pkg/linters/uncheckedflushreturn\"\n \t\"github.com/github/gh-aw/pkg/linters/uncheckedtypeassertion\"\n \t\"github.com/github/gh-aw/pkg/linters/walkfuncerrshadow\"\n \t\"github.com/github/gh-aw/pkg/linters/wgdonenotdeferred\"\n@@ -134,6 +135,7 @@ var allAnalyzers = []*analysis.Analyzer{\n \ttolowerequalfold.Analyzer,\n \ttrimleftright.Analyzer,\n \tuncheckedtypeassertion.Analyzer,\n+\tuncheckedflushreturn.Analyzer,\n \twalkfuncerrshadow.Analyzer,\n \twgdonenotdeferred.Analyzer,\n \twritebytestring.Analyzer,diff --git a/pkg/linters/spec_test.go b/pkg/linters/spec_test.go\nindex ccac35b7ad2..394b634d283 100644\n--- a/pkg/linters/spec_test.go\n+++ b/pkg/linters/spec_test.go\n@@ -68,6 +68,7 @@ import (\n \t\"github.com/github/gh-aw/pkg/linters/timesleepnocontext\"\n \t\"github.com/github/gh-aw/pkg/linters/tolowerequalfold\"\n \t\"github.com/github/gh-aw/pkg/linters/trimleftright\"\n+\t\"github.com/github/gh-aw/pkg/linters/uncheckedflushreturn\"\n \t\"github.com/github/gh-aw/pkg/linters/uncheckedtypeassertion\"\n \t\"github.com/github/gh-aw/pkg/linters/walkfuncerrshadow\"\n \t\"github.com/github/gh-aw/pkg/linters/wgdonenotdeferred\"\n@@ -87,7 +88,7 @@ type docAnalyzer struct {\n }\n \n // documentedAnalyzers returns the analyzer subpackages documented in the README\n-// \"Public API > Subpackages\" table. The README documents 61 analyzers\n+// \"Public API > Subpackages\" table. The README documents 62 analyzers\n // subpackages (the non-analyzer `internal` helper subpackage is excluded because\n // it exposes no Analyzer).\n //\n@@ -99,7 +100,7 @@ type docAnalyzer struct {\n //\tlogfatallibrary, manualmutexunlock, mapclearloop, mapdeletecheck, nilctxpassed, osexitinlibrary, osgetenvlibrary, ossetenvlibrary, panic-in-library-code, rawloginlib,\n //\tregexpcompileinfunction, seenmapbool, sortslice, sprintferrdot, sprintferrorsnew, sprintfbool, sprintfint, ssljson,\n //\tstrconvparseignorederror, stringbytesroundtrip, stringreplaceminusone, stringsconcatloop, stringscountcontains, stringsindexcontains, stringsindexhasprefix, stringsjoinone, timeafterleak, timesleepnocontext, timenowsub,\n-//\ttolowerequalfold, trimleftright, uncheckedtypeassertion, walkfuncerrshadow, wgdonenotdeferred, writebytestring\n+//\ttolowerequalfold, trimleftright, uncheckedflushreturn, uncheckedtypeassertion, walkfuncerrshadow, wgdonenotdeferred, writebytestring\n func documentedAnalyzers() []docAnalyzer {\n \treturn []docAnalyzer{\n \t\t{\"appendbytestring\", appendbytestring.Analyzer},\n@@ -160,6 +161,7 @@ func documentedAnalyzers() []docAnalyzer {\n \t\t{\"tolowerequalfold\", tolowerequalfold.Analyzer},\n \t\t{\"trimleftright\", trimleftright.Analyzer},\n \t\t{\"uncheckedtypeassertion\", uncheckedtypeassertion.Analyzer},\n+\t\t{\"uncheckedflushreturn\", uncheckedflushreturn.Analyzer},\n \t\t{\"walkfuncerrshadow\", walkfuncerrshadow.Analyzer},\n \t\t{\"wgdonenotdeferred\", wgdonenotdeferred.Analyzer},\n \t\t{\"writebytestring\", writebytestring.Analyzer},diff --git a/pkg/linters/uncheckedflushreturn/testdata/src/uncheckedflushreturn/uncheckedflushreturn.go b/pkg/linters/uncheckedflushreturn/testdata/src/uncheckedflushreturn/uncheckedflushreturn.go\nnew file mode 100644\nindex 00000000000..dc6ff53e2a6\n--- /dev/null\n+++ b/pkg/linters/uncheckedflushreturn/testdata/src/uncheckedflushreturn/uncheckedflushreturn.go\n@@ -0,0 +1,46 @@\n+package uncheckedflushreturn\n+\n+import (\n+\t\"bufio\"\n+\t\"strings\"\n+\t\"text/tabwriter\"\n+)\n+\n+func bad() {\n+\tvar sb strings.Builder\n+\tbw := bufio.NewWriter(&sb)\n+\tbw.Flush() // want `error return from Flush\\(\\) is discarded`\n+\n+\ttw := tabwriter.NewWriter(&sb, 0, 0, 1, ' ', 0)\n+\ttw.Flush() // want `error return from Flush\\(\\) is discarded`\n+}\n+\n+func badBlankAssign() {\n+\tvar sb strings.Builder\n+\tbw := bufio.NewWriter(&sb)\n+\t_ = bw.Flush() // want `error return from Flush\\(\\) is discarded`\n+}\n+\n+func good() {\n+\tvar sb strings.Builder\n+\tbw := bufio.NewWriter(&sb)\n+\tif err := bw.Flush(); err != nil {\n+\t\t_ = err\n+\t}\n+\n+\ttw := tabwriter.NewWriter(&sb, 0, 0, 1, ' ', 0)\n+\terr := tw.Flush()\n+\tif err != nil {\n+\t\t_ = err\n+\t}\n+}\n+\n+func suppressed() {\n+\tvar sb strings.Builder\n+\tbw := bufio.NewWriter(&sb)\n+\t//nolint:uncheckedflushreturn\n+\tbw.Flush()\n+\n+\ttw := tabwriter.NewWriter(&sb, 0, 0, 1, ' ', 0)\n+\ttw.Flush() //nolint:uncheckedflushreturn\n+}diff --git a/pkg/linters/uncheckedflushreturn/uncheckedflushreturn.go b/pkg/linters/uncheckedflushreturn/uncheckedflushreturn.go\nnew file mode 100644\nindex 00000000000..4d40b4460a3\n--- /dev/null\n+++ b/pkg/linters/uncheckedflushreturn/uncheckedflushreturn.go\n@@ -0,0 +1,136 @@\n+// Package uncheckedflushreturn implements a Go analysis linter that flags\n+// Flush() method calls where the error return is discarded.\n+package uncheckedflushreturn\n+\n+import (\n+\t\"go/ast\"\n+\t\"go/types\"\n+\n+\t\"golang.org/x/tools/go/analysis\"\n+\t\"golang.org/x/tools/go/analysis/passes/inspect\"\n+\n+\t\"github.com/github/gh-aw/pkg/linters/internal/astutil\"\n+\t\"github.com/github/gh-aw/pkg/linters/internal/filecheck\"\n+\t\"github.com/github/gh-aw/pkg/linters/internal/nolint\"\n+\t\"github.com/github/gh-aw/pkg/logger\"\n+)\n+\n+var pkgLog = logger.New(\"linters:uncheckedflushreturn\")\n+\n+// Analyzer is the unchecked-flush-return analysis pass.\n+var Analyzer = &analysis.Analyzer{\n+\tName:     \"uncheckedflushreturn\",\n+\tDoc:      \"reports Flush() method calls where the error return is discarded\",\n+\tURL:      \"https://github.com/github/gh-aw/tree/main/pkg/linters/uncheckedflushreturn\",\n+\tRequires: []*analysis.Analyzer{inspect.Analyzer, nolint.Analyzer, filecheck.Analyzer},\n+\tRun:      run,\n+}\n+\n+func run(pass *analysis.Pass) (any, error) {\n+\tpkgLog.Printf(\"analyzing package %s\", pass.Pkg.Path())\n+\n+\tinsp, err := astutil.Inspector(pass)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tnoLintIndex, err := nolint.Index(pass)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tgeneratedFiles, err := filecheck.Index(pass)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tnodeFilter := []ast.Node{(*ast.ExprStmt)(nil), (*ast.AssignStmt)(nil)}\n+\tinsp.Preorder(nodeFilter, func(n ast.Node) {\n+\t\tswitch stmt := n.(type) {\n+\t\tcase *ast.ExprStmt:\n+\t\t\tposition := pass.Fset.PositionFor(stmt.Pos(), false)\n+\t\t\tif filecheck.ShouldSkipFilename(position.Filename, generatedFiles) {\n+\t\t\t\treturn\n+\t\t\t}\n+\t\t\tcheckDiscardedFlushExpr(pass, stmt, noLintIndex)\n+\t\tcase *ast.AssignStmt:\n+\t\t\tposition := pass.Fset.PositionFor(stmt.Pos(), false)\n+\t\t\tif filecheck.ShouldSkipFilename(position.Filename, generatedFiles) {\n+\t\t\t\treturn\n+\t\t\t}\n+\t\t\tcheckDiscardedFlushAssign(pass, stmt, noLintIndex)\n+\t\t}\n+\t})\n+\treturn nil, nil\n+}\n+\n+// checkDiscardedFlushExpr flags Flush() used as a bare expression statement,\n+// where the error return is silently discarded.\n+func checkDiscardedFlushExpr(pass *analysis.Pass, stmt *ast.ExprStmt, noLintIndex nolint.DirectiveIndex) {\n+\tcall, ok := stmt.X.(*ast.CallExpr)\n+\tif !ok {\n+\t\treturn\n+\t}\n+\tif !isFlushCallReturningError(pass, call) {\n+\t\treturn\n+\t}\n+\treportUncheckedFlush(pass, call, noLintIndex)\n+}\n+\n+// checkDiscardedFlushAssign flags _ = x.Flush() assignments where the error\n+// is explicitly thrown away with a blank identifier.\n+func checkDiscardedFlushAssign(pass *analysis.Pass, assign *ast.AssignStmt, noLintIndex nolint.DirectiveIndex) {\n+\tif len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {\n+\t\treturn\n+\t}\n+\tblank, ok := assign.Lhs[0].(*ast.Ident)\n+\tif !ok || blank.Name != \"_\" {\n+\t\treturn\n+\t}\n+\tcall, ok := assign.Rhs[0].(*ast.CallExpr)\n+\tif !ok {\n+\t\treturn\n+\t}\n+\tif !isFlushCallReturningError(pass, call) {\n+\t\treturn\n+\t}\n+\treportUncheckedFlush(pass, call, noLintIndex)\n+}\n+\n+func reportUncheckedFlush(pass *analysis.Pass, call *ast.CallExpr, noLintIndex nolint.DirectiveIndex) {\n+\tposition := pass.Fset.PositionFor(call.Pos(), false)\n+\tif nolint.HasDirectiveForLinter(position, noLintIndex, \"uncheckedflushreturn\") {\n+\t\treturn\n+\t}\n+\tpkgLog.Printf(\"flagging unchecked Flush() error at %s:%d\", position.Filename, position.Line)\n+\tpass.ReportRangef(call, \"error return from Flush() is discarded; flush failures silently drop buffered data\")\n+}\n+\n+// isFlushCallReturningError returns true when call is a method named Flush\n+// that takes no arguments and returns a single error value.\n+func isFlushCallReturningError(pass *analysis.Pass, call *ast.CallExpr) bool {\n+\tsel, ok := call.Fun.(*ast.SelectorExpr)\n+\tif !ok {\n+\t\treturn false\n+\t}\n+\tif sel.Sel.Name != \"Flush\" {\n+\t\treturn false\n+\t}\n+\tif len(call.Args) != 0 {\n+\t\treturn false\n+\t}\n+\tsig, ok := pass.TypesInfo.TypeOf(call.Fun).(*types.Signature)\n+\tif !ok {\n+\t\treturn false\n+\t}\n+\tres := sig.Results()\n+\tif res.Len() != 1 {\n+\t\treturn false\n+\t}\n+\t_, isErr := res.At(0).Type().Underlying().(*types.Interface)\n+\treturn isErr && types.Implements(res.At(0).Type(), errorInterface)\n+}\n+\n+// errorInterface is the built-in error interface type.\n+var errorInterface = func() *types.Interface {\n+\terrorType := types.Universe.Lookup(\"error\").Type()\n+\treturn errorType.Underlying().(*types.Interface)\n+}()diff --git a/pkg/linters/uncheckedflushreturn/uncheckedflushreturn_test.go b/pkg/linters/uncheckedflushreturn/uncheckedflushreturn_test.go\nnew file mode 100644\nindex 00000000000..1298bbc341a\n--- /dev/null\n+++ b/pkg/linters/uncheckedflushreturn/uncheckedflushreturn_test.go\n@@ -0,0 +1,16 @@\n+//go:build !integration\n+\n+package uncheckedflushreturn_test\n+\n+import (\n+\t\"testing\"\n+\n+\t\"golang.org/x/tools/go/analysis/analysistest\"\n+\n+\t\"github.com/github/gh-aw/pkg/linters/uncheckedflushreturn\"\n+)\n+\n+func TestAnalyzer(t *testing.T) {\n+\ttestdata := analysistest.TestData()\n+\tanalysistest.Run(t, testdata, uncheckedflushreturn.Analyzer, \"uncheckedflushreturn\")\n+}\n<shellId: 11 completed with exit code 0>","detailedContent":"diff --git a/linters b/linters\nindex d920e6a6151..5725ab9455a 100755\nBinary files a/linters and b/linters differ\ndiff --git a/pkg/cli/logs_format_compact.go b/pkg/cli/logs_format_compact.go\nindex ae0dac8f09d..05ba00a8012 100644\n--- a/pkg/cli/logs_format_compact.go\n+++ b/pkg/cli/logs_format_compact.go\n@@ -137,7 +137,9 @@ func renderLogsCompactToWriter(w io.Writer, data LogsData) {\n \t\t\tr.TokenUsage, formatCompactAIC(r.AIC), r.Turns, r.ErrorCount,\n \t\t\tr.Event, actor, branch)\n \t}\n-\ttw.Flush()\n+\tif err := tw.Flush(); err != nil {\n+\t\tlogsCompactLog.Printf(\"flush error: %v\", err)\n+\t}\n \n \t// [errors] — aggregated error/warning messages\n \tif len(data.ErrorsAndWarnings) > 0 {\n@@ -317,7 +319,9 @@ func renderLogsCompactVerboseToWriter(w io.Writer, data LogsData) {\n \t\t\tr.Event, actor, tbt, classification,\n \t\t\tr.CreatedAt.Format(\"01-02 15:04\"), r.Branch)\n \t}\n-\ttw.Flush()\n+\tif err := tw.Flush(); err != nil {\n+\t\tlogsCompactLog.Printf(\"flush error: %v\", err)\n+\t}\n \n \t// [errors]\n \tif len(data.ErrorsAndWarnings) > 0 {diff --git a/pkg/linters/README.md b/pkg/linters/README.md\nindex 6ee1ea8b056..836eb89dfdb 100644\n--- a/pkg/linters/README.md\n+++ b/pkg/linters/README.md\n@@ -64,6 +64,7 @@ This package currently provides custom Go analyzers in the following subpackages\n - `tolowerequalfold` — reports case-insensitive string comparisons using `strings.ToLower`/`ToUpper` that should use `strings.EqualFold`.\n - `trimleftright` — reports `strings.TrimLeft`/`TrimRight` calls with a multi-character literal cutset where `TrimPrefix`/`TrimSuffix` was likely intended.\n - `uncheckedtypeassertion` — reports single-value type assertions where unchecked panics are possible.\n+- `uncheckedflushreturn` — reports `Flush()` method calls where the error return is discarded, which silently drops buffered data on failure.\n - `wgdonenotdeferred` — reports non-deferred `sync.WaitGroup.Done()` calls that can deadlock on panics or early returns.\n - `writebytestring` — reports `w.Write([]byte(s))` calls where `s` is a string, which can be replaced with `io.WriteString` to avoid an unnecessary `[]byte` allocation.\n - `internal` — shared helper packages for analyzers (file checks and `nolint` handling).\n@@ -132,6 +133,7 @@ This package currently provides custom Go analyzers in the following subpackages\n | `tolowerequalfold` | Custom `go/analysis` analyzer that flags case-insensitive comparisons via `strings.ToLower`/`ToUpper` that should use `strings.EqualFold` |\n | `trimleftright` | Custom `go/analysis` analyzer that flags `strings.TrimLeft`/`TrimRight` calls with a multi-character literal cutset where `TrimPrefix`/`TrimSuffix` was likely intended |\n | `uncheckedtypeassertion` | Custom `go/analysis` analyzer that flags unchecked single-value type assertions |\n+| `uncheckedflushreturn` | Custom `go/analysis` analyzer that flags `Flush()` method calls where the error return is discarded |\n | `walkfuncerrshadow` | Custom `go/analysis` analyzer that flags `filepath.Walk`/`filepath.WalkDir` callbacks whose `err` parameter shadows an outer `err` variable assigned from the walk call |\n | `wgdonenotdeferred` | Custom `go/analysis` analyzer that flags non-deferred `sync.WaitGroup.Done()` calls |\n | `writebytestring` | Custom `go/analysis` analyzer that flags `w.Write([]byte(s))` calls where `s` is a string that can be replaced with `io.WriteString` |\n@@ -262,6 +264,7 @@ _ = trimleftright.Analyzer\n - `github.com/github/gh-aw/pkg/linters/tolowerequalfold` — to-lower-equal-fold analyzer subpackage\n - `github.com/github/gh-aw/pkg/linters/trimleftright` — trim-left-right analyzer subpackage\n - `github.com/github/gh-aw/pkg/linters/uncheckedtypeassertion` — unchecked-type-assertion analyzer subpackage\n+- `github.com/github/gh-aw/pkg/linters/uncheckedflushreturn` — unchecked-flush-return analyzer subpackage\n - `github.com/github/gh-aw/pkg/linters/walkfuncerrshadow` — walk-func-err-shadow analyzer subpackage\n - `github.com/github/gh-aw/pkg/linters/wgdonenotdeferred` — wg-done-not-deferred analyzer subpackage\n - `github.com/github/gh-aw/pkg/linters/writebytestring` — write-byte-string analyzer subpackagediff --git a/pkg/linters/doc.go b/pkg/linters/doc.go\nindex 53ef264705b..3a0f83c8de4 100644\n--- a/pkg/linters/doc.go\n+++ b/pkg/linters/doc.go\n@@ -1,6 +1,6 @@\n // Package linters is a namespace for gh-aw's custom Go analysis linters.\n //\n-// All 61 active analyzers:\n+// All 62 active analyzers:\n //\n //   - appendbytestring — flags append(b, []byte(s)...) calls where s is a string that can be simplified to append(b, s...)\n //   - appendoneelement — flags append(s, []T{x}...) calls where a single-element slice literal is spread and can be simplified to append(s, x)\n@@ -60,6 +60,7 @@\n //   - tolowerequalfold — flags case-insensitive comparisons via ToLower/ToUpper that should use EqualFold\n //   - trimleftright — flags strings.TrimLeft/TrimRight calls with a multi-character literal cutset where TrimPrefix/TrimSuffix was likely intended\n //   - uncheckedtypeassertion — flags unchecked single-value type assertions\n+//   - uncheckedflushreturn — flags Flush() method calls where the error return is discarded\n //   - walkfuncerrshadow — flags filepath.Walk/WalkDir callbacks whose err parameter shadows an outer err variable assigned from the walk call\n //   - wgdonenotdeferred — flags non-deferred sync.WaitGroup.Done() calls\n //   - writebytestring — flags w.Write([]byte(s)) calls where s is a string that can be replaced with io.WriteString(w, s)diff --git a/pkg/linters/registry.go b/pkg/linters/registry.go\nindex f20b15e227d..af0285ac238 100644\n--- a/pkg/linters/registry.go\n+++ b/pkg/linters/registry.go\n@@ -60,6 +60,7 @@ import (\n \t\"github.com/github/gh-aw/pkg/linters/timesleepnocontext\"\n \t\"github.com/github/gh-aw/pkg/linters/tolowerequalfold\"\n \t\"github.com/github/gh-aw/pkg/linters/trimleftright\"\n+\t\"github.com/github/gh-aw/pkg/linters/uncheckedflushreturn\"\n \t\"github.com/github/gh-aw/pkg/linters/uncheckedtypeassertion\"\n \t\"github.com/github/gh-aw/pkg/linters/walkfuncerrshadow\"\n \t\"github.com/github/gh-aw/pkg/linters/wgdonenotdeferred\"\n@@ -134,6 +135,7 @@ var allAnalyzers = []*analysis.Analyzer{\n \ttolowerequalfold.Analyzer,\n \ttrimleftright.Analyzer,\n \tuncheckedtypeassertion.Analyzer,\n+\tuncheckedflushreturn.Analyzer,\n \twalkfuncerrshadow.Analyzer,\n \twgdonenotdeferred.Analyzer,\n \twritebytestring.Analyzer,diff --git a/pkg/linters/spec_test.go b/pkg/linters/spec_test.go\nindex ccac35b7ad2..394b634d283 100644\n--- a/pkg/linters/spec_test.go\n+++ b/pkg/linters/spec_test.go\n@@ -68,6 +68,7 @@ import (\n \t\"github.com/github/gh-aw/pkg/linters/timesleepnocontext\"\n \t\"github.com/github/gh-aw/pkg/linters/tolowerequalfold\"\n \t\"github.com/github/gh-aw/pkg/linters/trimleftright\"\n+\t\"github.com/github/gh-aw/pkg/linters/uncheckedflushreturn\"\n \t\"github.com/github/gh-aw/pkg/linters/uncheckedtypeassertion\"\n \t\"github.com/github/gh-aw/pkg/linters/walkfuncerrshadow\"\n \t\"github.com/github/gh-aw/pkg/linters/wgdonenotdeferred\"\n@@ -87,7 +88,7 @@ type docAnalyzer struct {\n }\n \n // documentedAnalyzers returns the analyzer subpackages documented in the README\n-// \"Public API > Subpackages\" table. The README documents 61 analyzers\n+// \"Public API > Subpackages\" table. The README documents 62 analyzers\n // subpackages (the non-analyzer `internal` helper subpackage is excluded because\n // it exposes no Analyzer).\n //\n@@ -99,7 +100,7 @@ type docAnalyzer struct {\n //\tlogfatallibrary, manualmutexunlock, mapclearloop, mapdeletecheck, nilctxpassed, osexitinlibrary, osgetenvlibrary, ossetenvlibrary, panic-in-library-code, rawloginlib,\n //\tregexpcompileinfunction, seenmapbool, sortslice, sprintferrdot, sprintferrorsnew, sprintfbool, sprintfint, ssljson,\n //\tstrconvparseignorederror, stringbytesroundtrip, stringreplaceminusone, stringsconcatloop, stringscountcontains, stringsindexcontains, stringsindexhasprefix, stringsjoinone, timeafterleak, timesleepnocontext, timenowsub,\n-//\ttolowerequalfold, trimleftright, uncheckedtypeassertion, walkfuncerrshadow, wgdonenotdeferred, writebytestring\n+//\ttolowerequalfold, trimleftright, uncheckedflushreturn, uncheckedtypeassertion, walkfuncerrshadow, wgdonenotdeferred, writebytestring\n func documentedAnalyzers() []docAnalyzer {\n \treturn []docAnalyzer{\n \t\t{\"appendbytestring\", appendbytestring.Analyzer},\n@@ -160,6 +161,7 @@ func documentedAnalyzers() []docAnalyzer {\n \t\t{\"tolowerequalfold\", tolowerequalfold.Analyzer},\n \t\t{\"trimleftright\", trimleftright.Analyzer},\n \t\t{\"uncheckedtypeassertion\", uncheckedtypeassertion.Analyzer},\n+\t\t{\"uncheckedflushreturn\", uncheckedflushreturn.Analyzer},\n \t\t{\"walkfuncerrshadow\", walkfuncerrshadow.Analyzer},\n \t\t{\"wgdonenotdeferred\", wgdonenotdeferred.Analyzer},\n \t\t{\"writebytestring\", writebytestring.Analyzer},diff --git a/pkg/linters/uncheckedflushreturn/testdata/src/uncheckedflushreturn/uncheckedflushreturn.go b/pkg/linters/uncheckedflushreturn/testdata/src/uncheckedflushreturn/uncheckedflushreturn.go\nnew file mode 100644\nindex 00000000000..dc6ff53e2a6\n--- /dev/null\n+++ b/pkg/linters/uncheckedflushreturn/testdata/src/uncheckedflushreturn/uncheckedflushreturn.go\n@@ -0,0 +1,46 @@\n+package uncheckedflushreturn\n+\n+import (\n+\t\"bufio\"\n+\t\"strings\"\n+\t\"text/tabwriter\"\n+)\n+\n+func bad() {\n+\tvar sb strings.Builder\n+\tbw := bufio.NewWriter(&sb)\n+\tbw.Flush() // want `error return from Flush\\(\\) is discarded`\n+\n+\ttw := tabwriter.NewWriter(&sb, 0, 0, 1, ' ', 0)\n+\ttw.Flush() // want `error return from Flush\\(\\) is discarded`\n+}\n+\n+func badBlankAssign() {\n+\tvar sb strings.Builder\n+\tbw := bufio.NewWriter(&sb)\n+\t_ = bw.Flush() // want `error return from Flush\\(\\) is discarded`\n+}\n+\n+func good() {\n+\tvar sb strings.Builder\n+\tbw := bufio.NewWriter(&sb)\n+\tif err := bw.Flush(); err != nil {\n+\t\t_ = err\n+\t}\n+\n+\ttw := tabwriter.NewWriter(&sb, 0, 0, 1, ' ', 0)\n+\terr := tw.Flush()\n+\tif err != nil {\n+\t\t_ = err\n+\t}\n+}\n+\n+func suppressed() {\n+\tvar sb strings.Builder\n+\tbw := bufio.NewWriter(&sb)\n+\t//nolint:uncheckedflushreturn\n+\tbw.Flush()\n+\n+\ttw := tabwriter.NewWriter(&sb, 0, 0, 1, ' ', 0)\n+\ttw.Flush() //nolint:uncheckedflushreturn\n+}diff --git a/pkg/linters/uncheckedflushreturn/uncheckedflushreturn.go b/pkg/linters/uncheckedflushreturn/uncheckedflushreturn.go\nnew file mode 100644\nindex 00000000000..4d40b4460a3\n--- /dev/null\n+++ b/pkg/linters/uncheckedflushreturn/uncheckedflushreturn.go\n@@ -0,0 +1,136 @@\n+// Package uncheckedflushreturn implements a Go analysis linter that flags\n+// Flush() method calls where the error return is discarded.\n+package uncheckedflushreturn\n+\n+import (\n+\t\"go/ast\"\n+\t\"go/types\"\n+\n+\t\"golang.org/x/tools/go/analysis\"\n+\t\"golang.org/x/tools/go/analysis/passes/inspect\"\n+\n+\t\"github.com/github/gh-aw/pkg/linters/internal/astutil\"\n+\t\"github.com/github/gh-aw/pkg/linters/internal/filecheck\"\n+\t\"github.com/github/gh-aw/pkg/linters/internal/nolint\"\n+\t\"github.com/github/gh-aw/pkg/logger\"\n+)\n+\n+var pkgLog = logger.New(\"linters:uncheckedflushreturn\")\n+\n+// Analyzer is the unchecked-flush-return analysis pass.\n+var Analyzer = &analysis.Analyzer{\n+\tName:     \"uncheckedflushreturn\",\n+\tDoc:      \"reports Flush() method calls where the error return is discarded\",\n+\tURL:      \"https://github.com/github/gh-aw/tree/main/pkg/linters/uncheckedflushreturn\",\n+\tRequires: []*analysis.Analyzer{inspect.Analyzer, nolint.Analyzer, filecheck.Analyzer},\n+\tRun:      run,\n+}\n+\n+func run(pass *analysis.Pass) (any, error) {\n+\tpkgLog.Printf(\"analyzing package %s\", pass.Pkg.Path())\n+\n+\tinsp, err := astutil.Inspector(pass)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tnoLintIndex, err := nolint.Index(pass)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tgeneratedFiles, err := filecheck.Index(pass)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tnodeFilter := []ast.Node{(*ast.ExprStmt)(nil), (*ast.AssignStmt)(nil)}\n+\tinsp.Preorder(nodeFilter, func(n ast.Node) {\n+\t\tswitch stmt := n.(type) {\n+\t\tcase *ast.ExprStmt:\n+\t\t\tposition := pass.Fset.PositionFor(stmt.Pos(), false)\n+\t\t\tif filecheck.ShouldSkipFilename(position.Filename, generatedFiles) {\n+\t\t\t\treturn\n+\t\t\t}\n+\t\t\tcheckDiscardedFlushExpr(pass, stmt, noLintIndex)\n+\t\tcase *ast.AssignStmt:\n+\t\t\tposition := pass.Fset.PositionFor(stmt.Pos(), false)\n+\t\t\tif filecheck.ShouldSkipFilename(position.Filename, generatedFiles) {\n+\t\t\t\treturn\n+\t\t\t}\n+\t\t\tcheckDiscardedFlushAssign(pass, stmt, noLintIndex)\n+\t\t}\n+\t})\n+\treturn nil, nil\n+}\n+\n+// checkDiscardedFlushExpr flags Flush() used as a bare expression statement,\n+// where the error return is silently discarded.\n+func checkDiscardedFlushExpr(pass *analysis.Pass, stmt *ast.ExprStmt, noLintIndex nolint.DirectiveIndex) {\n+\tcall, ok := stmt.X.(*ast.CallExpr)\n+\tif !ok {\n+\t\treturn\n+\t}\n+\tif !isFlushCallReturningError(pass, call) {\n+\t\treturn\n+\t}\n+\treportUncheckedFlush(pass, call, noLintIndex)\n+}\n+\n+// checkDiscardedFlushAssign flags _ = x.Flush() assignments where the error\n+// is explicitly thrown away with a blank identifier.\n+func checkDiscardedFlushAssign(pass *analysis.Pass, assign *ast.AssignStmt, noLintIndex nolint.DirectiveIndex) {\n+\tif len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {\n+\t\treturn\n+\t}\n+\tblank, ok := assign.Lhs[0].(*ast.Ident)\n+\tif !ok || blank.Name != \"_\" {\n+\t\treturn\n+\t}\n+\tcall, ok := assign.Rhs[0].(*ast.CallExpr)\n+\tif !ok {\n+\t\treturn\n+\t}\n+\tif !isFlushCallReturningError(pass, call) {\n+\t\treturn\n+\t}\n+\treportUncheckedFlush(pass, call, noLintIndex)\n+}\n+\n+func reportUncheckedFlush(pass *analysis.Pass, call *ast.CallExpr, noLintIndex nolint.DirectiveIndex) {\n+\tposition := pass.Fset.PositionFor(call.Pos(), false)\n+\tif nolint.HasDirectiveForLinter(position, noLintIndex, \"uncheckedflushreturn\") {\n+\t\treturn\n+\t}\n+\tpkgLog.Printf(\"flagging unchecked Flush() error at %s:%d\", position.Filename, position.Line)\n+\tpass.ReportRangef(call, \"error return from Flush() is discarded; flush failures silently drop buffered data\")\n+}\n+\n+// isFlushCallReturningError returns true when call is a method named Flush\n+// that takes no arguments and returns a single error value.\n+func isFlushCallReturningError(pass *analysis.Pass, call *ast.CallExpr) bool {\n+\tsel, ok := call.Fun.(*ast.SelectorExpr)\n+\tif !ok {\n+\t\treturn false\n+\t}\n+\tif sel.Sel.Name != \"Flush\" {\n+\t\treturn false\n+\t}\n+\tif len(call.Args) != 0 {\n+\t\treturn false\n+\t}\n+\tsig, ok := pass.TypesInfo.TypeOf(call.Fun).(*types.Signature)\n+\tif !ok {\n+\t\treturn false\n+\t}\n+\tres := sig.Results()\n+\tif res.Len() != 1 {\n+\t\treturn false\n+\t}\n+\t_, isErr := res.At(0).Type().Underlying().(*types.Interface)\n+\treturn isErr && types.Implements(res.At(0).Type(), errorInterface)\n+}\n+\n+// errorInterface is the built-in error interface type.\n+var errorInterface = func() *types.Interface {\n+\terrorType := types.Universe.Lookup(\"error\").Type()\n+\treturn errorType.Underlying().(*types.Interface)\n+}()diff --git a/pkg/linters/uncheckedflushreturn/uncheckedflushreturn_test.go b/pkg/linters/uncheckedflushreturn/uncheckedflushreturn_test.go\nnew file mode 100644\nindex 00000000000..1298bbc341a\n--- /dev/null\n+++ b/pkg/linters/uncheckedflushreturn/uncheckedflushreturn_test.go\n@@ -0,0 +1,16 @@\n+//go:build !integration\n+\n+package uncheckedflushreturn_test\n+\n+import (\n+\t\"testing\"\n+\n+\t\"golang.org/x/tools/go/analysis/analysistest\"\n+\n+\t\"github.com/github/gh-aw/pkg/linters/uncheckedflushreturn\"\n+)\n+\n+func TestAnalyzer(t *testing.T) {\n+\ttestdata := analysistest.TestData()\n+\tanalysistest.Run(t, testdata, uncheckedflushreturn.Analyzer, \"uncheckedflushreturn\")\n+}\n<shellId: 11 completed with exit code 0>"}}}
{"type":"assistant.message","timestamp":"2026-08-02T22:44:25.847Z","data":{"content":""}}
{"type":"tool.execution_start","timestamp":"2026-08-02T22:44:25.850Z","data":{"toolName":"task","mcpServerName":""}}
{"type":"subagent.started","timestamp":"2026-08-02T22:44:25.861Z","data":{"agentName":"general-purpose","agentDisplayName":"General Purpose Agent","toolCallId":"toolu_01JvJ94nm9o6LvnS5URp4WHc"}}
{"type":"tool.execution_complete","timestamp":"2026-08-02T22:44:25.869Z","data":{"toolName":"task","mcpServerName":"","success":true,"result":{"content":"Agent started in background with agent_id: grumpy-coder-3. You'll be notified when it completes. Tell the user you're waiting and end your response, or continue unrelated work until notified. The agent supports multi-turn conversations — use write_agent to send follow-up messages.","detailedContent":"Prompt to general-purpose agent (grumpy-coder-3):\nYou are a extremely critical, grumpy senior Go code reviewer doing first-pass issue mining on a pull request diff. Do NOT be nice. Find every plausible correctness bug, missed edge case, false negative/false positive in a static analyzer, concurrency issue, performance issue, or maintainability issue in the following diff. Only comment on lines that are actually changed/added in the diff (ignore unchanged context lines and binary file changes).\n\nThe PR adds a new go/analysis linter `uncheckedflushreturn` that flags `Flush()` calls whose `error` return is discarded, and fixes two call sites in pkg/cli/logs_format_compact.go.\n\nOutput STRICT JSONL only — one JSON object per line, no markdown, no prose before/after. Each line must have exactly these fields:\n{\"file\": \"<path>\", \"line\": <int line number in the new file version>, \"severity\": \"critical|high|medium|low\", \"issue\": \"<short title>\", \"explanation\": \"<why it matters>\"}\n\nHere is the full diff:\n\n```diff\ndiff --git a/pkg/cli/logs_format_compact.go b/pkg/cli/logs_format_compact.go\nindex ae0dac8f09d..05ba00a8012 100644\n--- a/pkg/cli/logs_format_compact.go\n+++ b/pkg/cli/logs_format_compact.go\n@@ -137,7 +137,9 @@ func renderLogsCompactToWriter(w io.Writer, data LogsData) {\n \t\t\tr.TokenUsage, formatCompactAIC(r.AIC), r.Turns, r.ErrorCount,\n \t\t\tr.Event, actor, branch)\n \t}\n-\ttw.Flush()\n+\tif err := tw.Flush(); err != nil {\n+\t\tlogsCompactLog.Printf(\"flush error: %v\", err)\n+\t}\n \n \t// [errors] — aggregated error/warning messages\n \tif len(data.ErrorsAndWarnings) > 0 {\n@@ -317,7 +319,9 @@ func renderLogsCompactVerboseToWriter(w io.Writer, data LogsData) {\n \t\t\tr.Event, actor, tbt, classification,\n \t\t\tr.CreatedAt.Format(\"01-02 15:04\"), r.Branch)\n \t}\n-\ttw.Flush()\n+\tif err := tw.Flush(); err != nil {\n+\t\tlogsCompactLog.Printf(\"flush error: %v\", err)\n+\t}\n \n \t// [errors]\n \tif len(data.ErrorsAndWarnings) > 0 {\ndiff --git a/pkg/linters/uncheckedflushreturn/uncheckedflushreturn.go b/pkg/linters/uncheckedflushreturn/uncheckedflushreturn.go\nnew file mode 100644\nindex 00000000000..4d40b4460a3\n--- /dev/null\n+++ b/pkg/linters/uncheckedflushreturn/uncheckedflushreturn.go\n@@ -0,0 +1,136 @@\n+// Package uncheckedflushreturn implements a Go analysis linter that flags\n+// Flush() method calls where the error return is discarded.\n+package uncheckedflushreturn\n+\n+import (\n+\t\"go/ast\"\n+\t\"go/types\"\n+\n+\t\"golang.org/x/tools/go/analysis\"\n+\t\"golang.org/x/tools/go/analysis/passes/inspect\"\n+\n+\t\"github.com/github/gh-aw/pkg/linters/internal/astutil\"\n+\t\"github.com/github/gh-aw/pkg/linters/internal/filecheck\"\n+\t\"github.com/github/gh-aw/pkg/linters/internal/nolint\"\n+\t\"github.com/github/gh-aw/pkg/logger\"\n+)\n+\n+var pkgLog = logger.New(\"linters:uncheckedflushreturn\")\n+\n+// Analyzer is the unchecked-flush-return analysis pass.\n+var Analyzer = &analysis.Analyzer{\n+\tName:     \"uncheckedflushreturn\",\n+\tDoc:      \"reports Flush() method calls where the error return is discarded\",\n+\tURL:      \"https://github.com/github/gh-aw/tree/main/pkg/linters/uncheckedflushreturn\",\n+\tRequires: []*analysis.Analyzer{inspect.Analyzer, nolint.Analyzer, filecheck.Analyzer},\n+\tRun:      run,\n+}\n+\n+func run(pass *analysis.Pass) (any, error) {\n+\tpkgLog.Printf(\"analyzing package %s\", pass.Pkg.Path())\n+\n+\tinsp, err := astutil.Inspector(pass)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tnoLintIndex, err := nolint.Index(pass)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tgeneratedFiles, err := filecheck.Index(pass)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tnodeFilter := []ast.Node{(*ast.ExprStmt)(nil), (*ast.AssignStmt)(nil)}\n+\tinsp.Preorder(nodeFilter, func(n ast.Node) {\n+\t\tswitch stmt := n.(type) {\n+\t\tcase *ast.ExprStmt:\n+\t\t\tposition := pass.Fset.PositionFor(stmt.Pos(), false)\n+\t\t\tif filecheck.ShouldSkipFilename(position.Filename, generatedFiles) {\n+\t\t\t\treturn\n+\t\t\t}\n+\t\t\tcheckDiscardedFlushExpr(pass, stmt, noLintIndex)\n+\t\tcase *ast.AssignStmt:\n+\t\t\tposition := pass.Fset.PositionFor(stmt.Pos(), false)\n+\t\t\tif filecheck.ShouldSkipFilename(position.Filename, generatedFiles) {\n+\t\t\t\treturn\n+\t\t\t}\n+\t\t\tcheckDiscardedFlushAssign(pass, stmt, noLintIndex)\n+\t\t}\n+\t})\n+\treturn nil, nil\n+}\n+\n+// checkDiscardedFlushExpr flags Flush() used as a bare expression statement,\n+// where the error return is silently discarded.\n+func checkDiscardedFlushExpr(pass *analysis.Pass, stmt *ast.ExprStmt, noLintIndex nolint.DirectiveIndex) {\n+\tcall, ok := stmt.X.(*ast.CallExpr)\n+\tif !ok {\n+\t\treturn\n+\t}\n+\tif !isFlushCallReturningError(pass, call) {\n+\t\treturn\n+\t}\n+\treportUncheckedFlush(pass, call, noLintIndex)\n+}\n+\n+// checkDiscardedFlushAssign flags _ = x.Flush() assignments where the error\n+// is explicitly thrown away with a blank identifier.\n+func checkDiscardedFlushAssign(pass *analysis.Pass, assign *ast.AssignStmt, noLintIndex nolint.DirectiveIndex) {\n+\tif len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {\n+\t\treturn\n+\t}\n+\tblank, ok := assign.Lhs[0].(*ast.Ident)\n+\tif !ok || blank.Name != \"_\" {\n+\t\treturn\n+\t}\n+\tcall, ok := assign.Rhs[0].(*ast.CallExpr)\n+\tif !ok {\n+\t\treturn\n+\t}\n+\tif !isFlushCallReturningError(pass, call) {\n+\t\treturn\n+\t}\n+\treportUncheckedFlush(pass, call, noLintIndex)\n+}\n+\n+func reportUncheckedFlush(pass *analysis.Pass, call *ast.CallExpr, noLintIndex nolint.DirectiveIndex) {\n+\tposition := pass.Fset.PositionFor(call.Pos(), false)\n+\tif nolint.HasDirectiveForLinter(position, noLintIndex, \"uncheckedflushreturn\") {\n+\t\treturn\n+\t}\n+\tpkgLog.Printf(\"flagging unchecked Flush() error at %s:%d\", position.Filename, position.Line)\n+\tpass.ReportRangef(call, \"error return from Flush() is discarded; flush failures silently drop buffered data\")\n+}\n+\n+// isFlushCallReturningError returns true when call is a method named Flush\n+// that takes no arguments and returns a single error value.\n+func isFlushCallReturningError(pass *analysis.Pass, call *ast.CallExpr) bool {\n+\tsel, ok := call.Fun.(*ast.SelectorExpr)\n+\tif !ok {\n+\t\treturn false\n+\t}\n+\tif sel.Sel.Name != \"Flush\" {\n+\t\treturn false\n+\t}\n+\tif len(call.Args) != 0 {\n+\t\treturn false\n+\t}\n+\tsig, ok := pass.TypesInfo.TypeOf(call.Fun).(*types.Signature)\n+\tif !ok {\n+\t\treturn false\n+\t}\n+\tres := sig.Results()\n+\tif res.Len() != 1 {\n+\t\treturn false\n+\t}\n+\t_, isErr := res.At(0).Type().Underlying().(*types.Interface)\n+\treturn isErr && types.Implements(res.At(0).Type(), errorInterface)\n+}\n+\n+// errorInterface is the built-in error interface type.\n+var errorInterface = func() *types.Interface {\n+\terrorType := types.Universe.Lookup(\"error\").Type()\n+\treturn errorType.Underlying().(*types.Interface)\n+}()\n```\n\nNote the analyzer only registers a node filter for `*ast.ExprStmt` and `*ast.AssignStmt` — think hard about whether `defer tw.Flush()`, goroutine `go tw.Flush()`, or Flush calls inside `select`/other statement forms are handled or missed, and whether that's a meaningful false-negative gap given Flush-in-defer is an extremely common real-world pattern. Also scrutinize the `isFlushCallReturningError` type-checking logic for correctness, and the error-handling additions in logs_format_compact.go (e.g., is logging sufficient, is there a risk the error is silently swallowed in a way that matters, is the variable name `logsCompactLog` correct/consistent). Report every issue you find, even minor ones — output nothing except the JSONL lines described above."}}}
[copilot-sdk-driver] [sdk-driver] error: Execution failed: Error: No model available. Check policy enablement under GitHub Settings > Copilot
{"type":"subagent.completed","timestamp":"2026-08-02T22:44:25.920Z","data":{"agentName":"general-purpose","toolCallId":"toolu_01JvJ94nm9o6LvnS5URp4WHc"}}

Action Required

Assign this issue to an agent to debug and fix the issue.

Debug with any coding agent

Use this prompt with any coding agent (GitHub Copilot, Claude, Gemini, etc.):

Debug the agentic workflow failure using https://raw.githubusercontent.com/github/gh-aw/main/debug.md

The failed workflow run is at https://github.com/github/gh-aw/actions/runs/30770509772
Manually invoke the agent

Debug this workflow failure using your favorite Agent CLI and the agentic-workflows prompt.

Tip

Stop reporting this workflow as a failure

To stop a workflow from creating failure issues, set report-failure-as-issue: false in its frontmatter:

safe-outputs:
  report-failure-as-issue: false

Generated from PR Code Quality Reviewer · 64.7 AIC ·

  • expires on Aug 3, 2026, 10:47 AM UTC

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