Summary
The custom Go analyzer panicinlibrarycode (pkg/linters/panic-in-library-code/) is registered in cmd/linters/main.go:43 and would fire on any non-test panic() call under pkg/. However, the CI step in .github/workflows/cgo.yml:1040 runs it with LINTER_FLAGS="-errstringmatch -test=false", which positively selects only the errstringmatch analyzer. The inline comment in cgo.yml acknowledges the situation: "Enforce the production errstringmatch analyzer without blocking on unrelated legacy custom analyzer findings in tests or other analyzer families."
Result: panicinlibrarycode is shipped, tested (analyzer self-test passes), but silently un-enforced. There are 16 production panic() sites in pkg/ today; none carry //nolint:panicinlibrarycode. Any new control-flow panic introduced into prod will not be flagged by CI.
This is #aw_sg17a1, identified in Sergo Run 17.
Evidence
- Linter source:
pkg/linters/panic-in-library-code/panic-in-library-code.go:18 registers Analyzer with doc "reports panic() calls in library code under pkg/ that should return errors instead". Skips test files and cmd/ packages.
- Registration:
cmd/linters/main.go:43 — panicinlibrarycode.Analyzer.
- CI invocation:
.github/workflows/cgo.yml:1040 — make golint-custom LINTER_FLAGS="-errstringmatch -test=false" (only errstringmatch enabled as a blocking gate).
- Serena LSP cross-check:
find_referencing_symbols on Analyzer returns exactly 2 references — cmd/linters/main.go:42 (registration) and panic-in-library-code_test.go:14 (analyzer self-test). No other CI or build integration.
- Production violations (16 sites —
grep -rn "panic(" pkg/ --include="*.go" | grep -v _test.go | grep -v testdata):
pkg/actionpins/actionpins.go:117 — sync.Once load of embedded action pins JSON
pkg/parser/virtual_fs.go:29, :38 — registration preconditions
pkg/testutil/tempdir.go:33, :42 — test-runs directory creation (testutil package, not in _test.go)
pkg/workflow/agentic_engine.go:463 — init() engine registration
pkg/workflow/cache.go:48 — defence-in-depth precondition on cacheID format (well documented)
pkg/workflow/claude_tools.go:179 — precondition in computeAllowedClaudeToolsString
pkg/workflow/domains.go:324 — embedded JSON load; :785 — BUG marker (validation should have caught)
pkg/workflow/engine_definition_loader.go:137 — embed walk
pkg/workflow/gh_cli_permissions.go:82, :141 — JSON load + invalid pattern
pkg/workflow/github_tool_to_toolset.go:23 — JSON load
pkg/workflow/model_aliases.go:84 — embedded JSON parse
pkg/workflow/permissions_toolset_data.go:44 — JSON load
pkg/workflow/pi_engine.go:139 — BUG marker (unreachable json.Marshal)
pkg/workflow/strings.go:176 — crypto/rand failure (fatal)
Most sites are idiomatic Go patterns (lazy sync.Once init-load of embedded data, BUG markers on logically-unreachable error paths, defence-in-depth preconditions). The linter does not differentiate these from genuine control-flow panics.
Severity
High (process/quality risk, not a runtime bug). A linter that exists but doesn't run is a maintenance liability — it suggests intent to enforce a rule, while the rule rots.
Recommendation
Choose one resolution; both unblock the analyzer for inclusion in the CI blocking gate:
Option A — Refine the linter (recommended): Teach panicinlibrarycode to skip the idiomatic patterns already established in this repo:
- Panics inside
*ast.FuncLit invoked from sync.Once.Do — these are lazy init.
- Panics where the message starts with
BUG: (or bug:) — explicit unreachable markers.
- Panics in
init() functions — registration failures are fatal at startup.
- Panics in functions whose doc comment contains
// Panics on or similar precondition contract.
This is consistent with how errstringmatch and largefunc were tuned for in-codebase reality.
Option B — Annotate: Add //nolint:panicinlibrarycode // <justification> above each of the 16 sites, then enable the analyzer in CI by changing the flag set in cgo.yml:1040 to include -panicinlibrarycode (or remove the positive selector and add -test=false exclusions for specific test-adjacent packages).
Option C — Retire: If the project no longer wants this rule, remove the linter package and its registration. Keeping unenforced rules invites confusion.
Validation
Related
- Established pattern (R7-R14): linter authors → CI integration → (nolint/redacted) annotations for defensible sites. Examples:
errstringmatch (3 refactors + 4 (nolint/redacted) in R14), manualmutexunlock (R13 audit), excessivefuncparams (R6 #aw_sg6p2).
- This finding is structurally identical to the
errstringmatch resolution from Run 14: the linter exists, but findings need to be classified as defensible vs not before CI can enforce it.
References:
Generated by 🤖 Sergo - Serena Go Expert · ● opu47 18.5M · ◷
Summary
The custom Go analyzer
panicinlibrarycode(pkg/linters/panic-in-library-code/) is registered incmd/linters/main.go:43and would fire on any non-testpanic()call underpkg/. However, the CI step in.github/workflows/cgo.yml:1040runs it withLINTER_FLAGS="-errstringmatch -test=false", which positively selects only theerrstringmatchanalyzer. The inline comment in cgo.yml acknowledges the situation: "Enforce the production errstringmatch analyzer without blocking on unrelated legacy custom analyzer findings in tests or other analyzer families."Result:
panicinlibrarycodeis shipped, tested (analyzer self-test passes), but silently un-enforced. There are 16 productionpanic()sites inpkg/today; none carry//nolint:panicinlibrarycode. Any new control-flow panic introduced into prod will not be flagged by CI.This is #aw_sg17a1, identified in Sergo Run 17.
Evidence
pkg/linters/panic-in-library-code/panic-in-library-code.go:18registersAnalyzerwith doc "reports panic() calls in library code under pkg/ that should return errors instead". Skips test files andcmd/packages.cmd/linters/main.go:43—panicinlibrarycode.Analyzer..github/workflows/cgo.yml:1040—make golint-custom LINTER_FLAGS="-errstringmatch -test=false"(only errstringmatch enabled as a blocking gate).find_referencing_symbolsonAnalyzerreturns exactly 2 references —cmd/linters/main.go:42(registration) andpanic-in-library-code_test.go:14(analyzer self-test). No other CI or build integration.grep -rn "panic(" pkg/ --include="*.go" | grep -v _test.go | grep -v testdata):pkg/actionpins/actionpins.go:117— sync.Once load of embedded action pins JSONpkg/parser/virtual_fs.go:29, :38— registration preconditionspkg/testutil/tempdir.go:33, :42— test-runs directory creation (testutil package, not in_test.go)pkg/workflow/agentic_engine.go:463— init() engine registrationpkg/workflow/cache.go:48— defence-in-depth precondition on cacheID format (well documented)pkg/workflow/claude_tools.go:179— precondition incomputeAllowedClaudeToolsStringpkg/workflow/domains.go:324— embedded JSON load;:785— BUG marker (validation should have caught)pkg/workflow/engine_definition_loader.go:137— embed walkpkg/workflow/gh_cli_permissions.go:82, :141— JSON load + invalid patternpkg/workflow/github_tool_to_toolset.go:23— JSON loadpkg/workflow/model_aliases.go:84— embedded JSON parsepkg/workflow/permissions_toolset_data.go:44— JSON loadpkg/workflow/pi_engine.go:139— BUG marker (unreachable json.Marshal)pkg/workflow/strings.go:176— crypto/rand failure (fatal)Most sites are idiomatic Go patterns (lazy
sync.Onceinit-load of embedded data, BUG markers on logically-unreachable error paths, defence-in-depth preconditions). The linter does not differentiate these from genuine control-flow panics.Severity
High (process/quality risk, not a runtime bug). A linter that exists but doesn't run is a maintenance liability — it suggests intent to enforce a rule, while the rule rots.
Recommendation
Choose one resolution; both unblock the analyzer for inclusion in the CI blocking gate:
Option A — Refine the linter (recommended): Teach
panicinlibrarycodeto skip the idiomatic patterns already established in this repo:*ast.FuncLitinvoked fromsync.Once.Do— these are lazy init.BUG:(orbug:) — explicit unreachable markers.init()functions — registration failures are fatal at startup.// Panics onor similar precondition contract.This is consistent with how
errstringmatchandlargefuncwere tuned for in-codebase reality.Option B — Annotate: Add
//nolint:panicinlibrarycode // <justification>above each of the 16 sites, then enable the analyzer in CI by changing the flag set incgo.yml:1040to include-panicinlibrarycode(or remove the positive selector and add-test=falseexclusions for specific test-adjacent packages).Option C — Retire: If the project no longer wants this rule, remove the linter package and its registration. Keeping unenforced rules invites confusion.
Validation
make golint-customlocally with the new flag set; confirm zero findings on./pkg/.....github/workflows/cgo.yml:1040to includepanicinlibrarycodein the blocking gate.pkg/linters/panic-in-library-code/testdata/covering the new skip patterns (Option A) or confirm existing tests still pass (Option B/C).Related
errstringmatch(3 refactors + 4 (nolint/redacted) in R14),manualmutexunlock(R13 audit),excessivefuncparams(R6 #aw_sg6p2).errstringmatchresolution from Run 14: the linter exists, but findings need to be classified as defensible vs not before CI can enforce it.References: