Experimental · used daily

gox

A strict, opinionated static analyzer for Go. Built to catch the silent bugs an LLM writing Go without supervision is most likely to introduce — be loud, be opinionated, fail closed.

See what it catches.

Run gox check ./... on a Go package. Every rule defaults to error and points at the exact line.

~/code/my-service
$ gox check ./...
internal/auth/login.go:42:6: errcheck: error return from db.Close() dropped
    hint: assign to _ =, return it, or annotate with // safe-ignore: <why>
internal/orders/transfer.go:118:5: namedargs: call to transfer(orderID, userID) needs labels
    hint: transfer(/* orderID */ a, /* userID */ b)
pkg/parser/lex.go:201:8: shadow: declaration of "err" shadows outer variable
cmd/worker/main.go:67:2: goroutine: fire-and-forget go run() without errgroup / WaitGroup / cancel
internal/probe/health.go:14:6: httptimeout: http.Get uses http.DefaultClient which has no timeout
    hint: build an explicit *http.Client with a Timeout, or use NewRequestWithContext
gox: 5 issue(s)

What it catches.

Each rule targets a high-frequency LLM bug class — silent failures that compile clean and pass tests.

errcheck
error return values dropped silently.
shadow
:= re-declaring an outer variable (except ok).
forcetypeassert
x := v.(T) without the comma-ok form.
namedargs
2+ args of the same basic type at user-code call sites without /* paramName */ labels.
exhaustive
Non-exhaustive switch on iota enums or sealed interfaces.
noglobals
Mutable package-level var declarations.
banany
any / interface{} without written justification.
bodyclose
*http.Response.Body left unclosed.
contextcheck
context.Background() / TODO() inside a function that already has a context.Context.
goroutine
go f() with no visible errgroup, WaitGroup, or CancelFunc.
errorlint
errors.Is / errors.As / %w instead of == / type-assert / %s on errors.
httptimeout
HTTP clients and shortcut calls must set an explicit Timeout.
The killer rule is namedargs. Two adjacent strings or ints at a user-defined call site force the caller to label them inline. It prevents transfer(orderID, userID) vs transfer(userID, orderID) — no compile error, no test failure, and the single most common silent-bug class in unsupervised AI-written Go.

Every rule has one annotation.

Suppressions require a reason after the colon. Empty reasons are ignored — the reason is the documentation.

CommentEffect
// safe-ignore: <why>Suppress errcheck, forcetypeassert, bodyclose, contextcheck on the same line.
// global-ok: <why>Allow a package-level var (noglobals).
// any-ok: <why>Allow any / interface{} (banany).
// goroutine-ok: <why>Allow a fire-and-forget go statement (goroutine).
// exhaustive-ok: <why>Accept default: as covering missing variants (exhaustive).
// timeout-ok: <why>Allow an HTTP call or client constructed without an explicit Timeout (httptimeout).

One go install away.

shell
$ go install github.com/mentasystems/gox/cmd/gox@latest

# analyze; exit 1 on any issue
$ gox check ./...

# list registered analyzers
$ gox list

# explain a single rule (markdown to stdout, or --json envelope)
$ gox explain bodyclose

# gox check && go build / go test
$ gox build ./...
$ gox test  ./...

Drop gox into a legacy codebase without 2000 errors on day 1.

gox baseline captures the current state of issues into a .gox-baseline.json at the module root. From then on gox check reports only new violations — existing surface stays silent until someone touches it. Commit the file so the rest of the team gets the same view.

shell
$ gox baseline
captured 1742 issue(s) into .gox-baseline.json
from now on, `gox check` will report only NEW issues.

$ gox check ./...
gox: 0 issue(s)

# ... a new violation lands in a new commit ...
$ gox check ./...
internal/orders/new_handler.go:54:2: errcheck: error return from tx.Commit() dropped
gox: 1 issue(s)

Wire it into your agent in one command.

gox install claude drops a Stop hook into ~/.claude/settings.json. When Claude finishes a turn, gox runs once on every package whose .go files changed. If anything fails, the hook returns a decision:block JSON blob with the full output — Claude sees it on the next turn and has to fix or annotate before continuing.

shell
$ gox install claude
 wrote ~/.claude/gox-hook.sh
 registered Stop hook (30s)
# idempotent — re-run to refresh the script.
Claude Code only re-reads settings.json when the /hooks menu is opened or the app restarts — open /hooks once and the hook activates in the current session. Earlier versions used a PostToolUse hook that fired on every edit; the Stop hook runs once per turn and is noticeably faster on large packages.

A tool, not a new language.

"Agent-native languages" are a tempting bet — reinvent the surface so the agent never has room to be wrong. The trade-off is heavy: new syntax the models don't know, a stdlib that has to be rebuilt, and design churn while the contract settles. gox takes the opposite path: leave Go alone and gate the silent bug classes at the door.

goxa fresh agent language
Ecosystemevery Go library, day 1rebuild it
Model fluencyfrontier models already write Goteach them, line by line
StabilityGo 1.x compatibility promisev0.x while the language settles
Adoptiongo install, opt-in per repoport the codebase
Cost of being wronguninstall itrewrite back
You don't need a new language to make agent-written Go safe. You need strict default-error rules at the exact bug classes that compile clean and pass tests. That's the whole job.

Pure Go. No external linters.

A pure-Go implementation on top of go/ast, go/types, and go list -json. Per-package cache keyed by file mtime+size and the analyzer-set hash.

~9.4s
Cold run · no cache
~2.6s
Warm run · full cache
442pkg
~1800 .go files

Cache lives under $XDG_CACHE_HOME/gox/v2 (or ~/.cache/gox/v2). Pass --no-cache to disable.

Three principles.

zero deps
Go stdlib + go list -json. No x/tools, no third-party linter packages. When a new Go release lands, there's nothing to update.
fail closed
Every rule defaults to error. Opt-outs require an explicit annotation with a written reason. The reason is the doc.
tuned for LLMs
Heuristics catch high-frequency LLM bug classes without flooding human idioms — shadow exempts ok but flags err re-declaration, which is exactly the bug you want.