Gate sc on static analysis and a coverage ratchet #26

Merged
geusebio merged 3 commits from feature/static-analysis-and-coverage-gate into main 2026-08-14 21:00:47 +00:00
Owner

Companion to scclient #4, which explains the shape. Same .golangci.yaml, hack/lint.sh and hack/coverage.sh.

What it found here, and why the exclusions are narrow

os.Exit does not run deferred functions. main had defer stop() for the signal handler and then os.Exit(1) on the failing path — teardown that reads as if it runs and doesn't. stop() is called explicitly now (gocritic's exitAfterDefer).

Eight unchecked Close calls, answered one at a time. Excluding (*os.File).Close wholesale would have silenced all eight in one line — and would also have silenced a write close, which is the case that actually loses data. Every one of these is a read or an already-failing path, so each gets _ = and the linter keeps watching the writes. gzip.Reader and io.PipeReader are read-only by construction, so those two live in the config.

Four gosec findings, all this program reading files the user pointed it at — a .sc.yml, .git/HEAD, a build context — plus a spinner picking a random face. Each carries a #nosec with the reason on the line, the same way the platform repo's Dockerfiles carry hadolint ignore pragmas.

Floors recorded

ui 97.3, config 92.2, detect 87.1, upload 81.2, cli 80.7, bundle 75.6, selfupdate 73.7, cmd/sc 0.0.

go build, go vet, go test ./... and hack/lint.sh all clean.

Companion to [scclient #4](https://git.grey.ooo/Grey.ooo/someones.computer_scclient/pulls/4), which explains the shape. Same `.golangci.yaml`, `hack/lint.sh` and `hack/coverage.sh`. ## What it found here, and why the exclusions are narrow **`os.Exit` does not run deferred functions.** `main` had `defer stop()` for the signal handler and then `os.Exit(1)` on the failing path — teardown that reads as if it runs and doesn't. `stop()` is called explicitly now (gocritic's `exitAfterDefer`). **Eight unchecked `Close` calls, answered one at a time.** Excluding `(*os.File).Close` wholesale would have silenced all eight in one line — and would also have silenced a *write* close, which is the case that actually loses data. Every one of these is a read or an already-failing path, so each gets `_ =` and the linter keeps watching the writes. `gzip.Reader` and `io.PipeReader` are read-only by construction, so those two live in the config. **Four gosec findings, all this program reading files the user pointed it at** — a `.sc.yml`, `.git/HEAD`, a build context — plus a spinner picking a random face. Each carries a `#nosec` with the reason on the line, the same way the platform repo's Dockerfiles carry `hadolint ignore` pragmas. ## Floors recorded `ui` 97.3, `config` 92.2, `detect` 87.1, `upload` 81.2, `cli` 80.7, `bundle` 75.6, `selfupdate` 73.7, `cmd/sc` 0.0. `go build`, `go vet`, `go test ./...` and `hack/lint.sh` all clean.
Gate sc on static analysis and a coverage ratchet
Some checks failed
CI / build (pull_request) Failing after 52s
6c84bf4720
golangci-lint from a pinned image — staticcheck and friends plus bodyclose,
errorlint, gosec, gocritic and misspell — and a coverage ratchet in place of
a threshold, both matching scclient so the three Go repos gate the same way.

It found two things worth having found. os.Exit does not run deferred
functions, so main's `defer stop()` was a signal-handler teardown that never
ran on the failing path; it is called explicitly now. And eight unchecked
Close calls, which are answered one at a time rather than by excluding
*os.File wholesale — every one here is a read, and the exclusion that would
have silenced them would also have silenced a *write* close, which is the
case that actually loses data.

The gosec findings are all this program reading files the user pointed it at
— a .sc.yml, .git/HEAD, a build context — answered with a pragma and a reason
on the line, in the style the platform repo's Dockerfile lints use.
Run the linter from PATH in CI, where a bind mount reads nothing
Some checks failed
CI / build (pull_request) Failing after 1m0s
cfbe64a9da
A CI job step and the docker daemon are in different filesystem namespaces,
so a `-v "$PWD":/app` in a step mounts an empty directory and the linter
passes having checked nothing. Same fix as scclient: prefer a PATH binary at
exactly the pinned version, keep the image as the laptop path.
Record the Linux coverage figure, which is the one CI gates on
All checks were successful
CI / build (pull_request) Successful in 1m47s
adc23e2a6d
The ratchet caught selfupdate falling from 73.7% to 71.2% — not a regression
but a platform: it compiles a different replace.go per GOOS, so the
denominator differs, and the floor had been recorded from a laptop running
darwin. CI is what the floors gate, so CI's platform is what they record.
Sign in to join this conversation.
No reviewers
No labels
in-progress
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Grey.ooo/someones.computer_agent!26
No description provided.