Skip to content

[linter-miner] feat(linters): add osexitinlibrary linter#32448

Merged
pelikhan merged 4 commits into
mainfrom
linter-miner/os-exit-in-library-da919ebac5ed17a3
May 15, 2026
Merged

[linter-miner] feat(linters): add osexitinlibrary linter#32448
pelikhan merged 4 commits into
mainfrom
linter-miner/os-exit-in-library-da919ebac5ed17a3

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot commented May 15, 2026

Summary

Adds a new osexitinlibrary custom linter that reports os.Exit calls inside library (pkg/) packages.

What it catches

Calling os.Exit in a library package:

  • Bypasses all deferred cleanup — file closes, mutex unlocks, cleanup goroutines are all skipped
  • Makes code untestable — the process terminates instead of returning an error
  • Violates Go convention — only cmd/ entry-points should terminate the process

The linter flags any os.Exit(...) call in a package whose import path does not contain /cmd/ and does not end in /main.

Evidence

Found during automated code-pattern scan (run #4):

File Line Pattern
pkg/cli/upgrade_command.go 178 os.Exit(0) after successful upgrade — skips all deferred cleanup
pkg/cli/upgrade_command.go 376 os.Exit(exitErr.ExitCode()) propagating a child process exit code

Implementation

  • pkg/linters/osexitinlibrary/osexitinlibrary.go — AST walk over ast.CallExpr nodes; skips cmd/ packages
  • pkg/linters/osexitinlibrary/osexitinlibrary_test.go!integration-tagged test using analysistest
  • pkg/linters/osexitinlibrary/testdata/src/osexitinlibrary/osexitinlibrary.go — fixture with one flagged and one clean case
  • cmd/linters/main.go — registered osexitinlibrary.Analyzer in multichecker.Main

Verification

go build ./cmd/linters   ✅
go test ./pkg/linters/osexitinlibrary/...   ✅ PASS

Opened by linter-miner workflow run #25933000380

Generated by Linter Miner · ● 53.4M ·

  • expires on May 22, 2026, 6:17 PM UTC

pr-sous-chef: updated branch during run https://github.com/github/gh-aw/actions/runs/25942653140

Generated by 👨‍🍳 PR Sous Chef ·

Reports os.Exit calls inside library (pkg/) packages. Calling os.Exit
in a library package bypasses all deferred cleanup (file closes, mutex
unlocks, cleanup goroutines) and makes the code path impossible to test.
Only cmd/ entry-points should terminate the process.

Evidence from code scan:
- pkg/cli/upgrade_command.go:178  — os.Exit(0) after successful upgrade
- pkg/cli/upgrade_command.go:376  — os.Exit(exitErr.ExitCode())

The linter skips packages whose import path contains /cmd/ or ends in
/main so that true entry-points are never flagged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added automation cookie Issue Monster Loves Cookies! go-linters labels May 15, 2026
@pelikhan pelikhan marked this pull request as ready for review May 15, 2026 18:35
Copilot AI review requested due to automatic review settings May 15, 2026 18:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new custom Go analyzer osexitinlibrary that flags os.Exit calls in non-cmd/ packages, and registers it with the cmd/linters multichecker driver.

Changes:

  • New analyzer that walks ast.CallExpr nodes and reports os.Exit(...) calls, skipping packages whose import path contains /cmd/ or ends in /main.
  • New analysistest-based unit test with a positive and negative fixture.
  • Registered the new analyzer alongside excessivefuncparams and largefunc in the multichecker entry-point.
Show a summary per file
File Description
pkg/linters/osexitinlibrary/osexitinlibrary.go Implements the analyzer with package-path-based skip logic.
pkg/linters/osexitinlibrary/osexitinlibrary_test.go analysistest-driven test, gated by !integration build tag.
pkg/linters/osexitinlibrary/testdata/src/osexitinlibrary/osexitinlibrary.go Fixture file with a flagged and a clean function.
cmd/linters/main.go Registers the new analyzer in multichecker.Main.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 4/4 changed files
  • Comments generated: 0

@github-actions github-actions Bot mentioned this pull request May 15, 2026
@github-actions
Copy link
Copy Markdown
Contributor Author

🧪 Test Quality Sentinel Report

Test Quality Score: 100/100

Excellent test quality

Metric Value
New/modified tests analyzed 1
✅ Design tests (behavioral contracts) 1 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 1 (100%)
Duplicate test clusters 0
Test inflation detected No
🚨 Coding-guideline violations None

Test Classification Details

Test File Classification Issues Detected
TestOsExitInLibrary pkg/linters/osexitinlibrary/osexitinlibrary_test.go:13 ✅ Design None

Flagged Tests — Requires Review

No tests flagged — all tests passed quality review.


Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 1 test — unit (//go:build !integration)
  • 🟨 JavaScript (*.test.cjs, *.test.js): 0 tests

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%).


📖 Understanding Test Classifications

Design Tests (High Value) verify what the system does:

  • Assert on observable outputs, return values, or state changes
  • Cover error paths and boundary conditions
  • Would catch a behavioral regression if deleted
  • Remain valid even after internal refactoring

Implementation Tests (Low Value) verify how the system does it:

  • Assert on internal function calls (mocking internals)
  • Only test the happy path with typical inputs
  • Break during legitimate refactoring even when behavior is correct
  • Give false assurance: they pass even when the system is wrong

Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.

🧪 Test quality analysis by Test Quality Sentinel · ● 4.1M ·

Copy link
Copy Markdown
Contributor Author

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 100/100. Test quality is excellent — 0% of new tests are implementation tests (threshold: 30%).

Copy link
Copy Markdown
Contributor Author

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · ● 4.9M

"github.com/github/gh-aw/pkg/linters/osexitinlibrary"
)

func TestOsExitInLibrary(t *testing.T) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The test only exercises the "flagged" case. The skip logic (strings.Contains(pkgPath, "/cmd/")) is the other code path and it's untested. A second testdata package (e.g. testdata/src/cmd/main/main.go) containing an os.Exit call that should not be flagged would verify that the allow-list works correctly and prevent regressions if the skip condition is ever changed.

// testdata/src/cmd/main/main.go
package main

import "os"

func main() {
    os.Exit(0) // should NOT be flagged
}

Then add to the test:

analysistest.Run(t, testdata, osexitinlibrary.Analyzer, "osexitinlibrary", "cmd/main")

if !ok {
return
}
if ident.Name == "os" && sel.Sel.Name == "Exit" {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The check ident.Name == "os" matches by identifier name only — it doesn't verify that the os identifier actually refers to the standard library os package. If a file imports a third-party package with the alias os (unusual but valid Go), the linter would produce a false positive. The golang.org/x/tools/go/analysis framework provides type information via pass.TypesInfo that lets you confirm the import path:

if obj := pass.TypesInfo.ObjectOf(sel.Sel); obj != nil {
    if fn, ok := obj.(*types.Func); ok {
        if fn.Pkg().Path() == "os" && fn.Name() == "Exit" {
            pass.Reportf(call.Pos(), ...)
        }
    }
}

This is the recommended pattern in the golang.org/x/tools/go/analysis documentation and makes the linter robust to aliased imports.

@github-actions
Copy link
Copy Markdown
Contributor Author

Could you confirm the new linter is covered by tests and ready for another review pass?

Generated by 👨‍🍳 PR Sous Chef ·

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot lint go, review all comments and reviews

@github-actions
Copy link
Copy Markdown
Contributor Author

@copilot review all comments

Please address the unresolved TDD feedback, then summarize the remaining blockers or next steps.

Generated by 👨‍🍳 PR Sous Chef ·

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented May 15, 2026

@copilot lint go, review all comments and reviews

Done in 235ceb0.

Ran Go lint/test checks:

  • make golint
  • go test ./pkg/linters/osexitinlibrary/...
  • go build ./cmd/linters

I also fixed a linting issue uncovered during review: osexitinlibrary now skips test packages/files (*.test packages and _test.go files), so it no longer reports os.Exit from test scaffolding while still reporting real library code usage.

Copilot AI requested a review from pelikhan May 15, 2026 21:48
@pelikhan pelikhan merged commit 66a6d5a into main May 15, 2026
@pelikhan pelikhan deleted the linter-miner/os-exit-in-library-da919ebac5ed17a3 branch May 15, 2026 21:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automation cookie Issue Monster Loves Cookies! go-linters

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants