revive — Find the Bug¶
Each scenario shows code or config that looks fine but produces a misleading lint result, missed finding, or broken CI. Find the defect, explain it, and fix it.
Bug 1 — Undocumented exported var¶
Bug: the exported rule requires a doc comment that starts with the identifier name on every exported identifier; Default has none. Fix: add a conforming doc comment:
// Default is the package-level default store used when no explicit store is supplied.
var Default = NewStore()
(Or unexport it: var defaultStore = NewStore().)
Bug 2 — if err != nil { return err } flagged¶
func load() (*Config, error) {
cfg, err := readConfig()
if err != nil {
return nil, err
}
return cfg, nil
}
func loadName() (string, error) {
name, err := readName()
if err != nil {
return name, err
}
return name, nil
}
The second function trips if-return:
Bug: loadName writes the trivial pattern if err != nil { return x, err }; return x, nil, which collapses to return name, err after the call. load is fine because it returns different values on the two paths (nil vs cfg). Fix:
Bug 3 — Capitalized error message¶
Bug: the error-strings rule enforces the Go convention that error strings are lowercase and unpunctuated, because they are often wrapped: fmt.Errorf("load: %w", err) produces "load: Not Found." — ugly. Fix:
Bug 4 — Unused parameter¶
Bug: unused-parameter fires because r is never read. The signature is fixed (http.HandlerFunc), so deleting r is not an option. Fix: rename to _ to signal intent:
Or, if the rule is wrong-for-context too often (callback signatures everywhere), disable it for that file and explain why.
Bug 5 — Id vs ID¶
Bug: var-naming enforces Go's initialism convention: ID, URL, HTTP, JSON, etc. must be all-caps, not title-case. Fix:
If this is on an existing public API, change carefully — the rename is a breaking change for callers.
Bug 6 — Two packages without // Package comment¶
internal/store/store.go:1:1: should have a package comment
internal/cache/cache.go:1:1: should have a package comment
Bug: package-comments requires every package to have a doc comment on the file containing the package clause (typically one file per package — the "doc" file). Fix: add a comment immediately above one package X clause in each:
Convention: put the package comment on the file whose name matches the package, or in a doc.go file.
Bug 7 — Ignored os.Remove error¶
Bug: unhandled-error fires because os.Remove returns an error that is silently discarded — a real bug if the removal needed to succeed. Fix: either handle it, or be explicit:
if err := os.Remove(path); err != nil && !errors.Is(err, fs.ErrNotExist) {
log.Printf("cleanup: %v", err)
}
If you genuinely want to ignore certain calls (e.g., fmt.Print*), whitelist them in the config:
Do not silence by assigning to _ everywhere — the rule still flags that on some setups, and even when it does not, it hides intent.
Bug 8 — Defaults disable a helpful rule¶
The team thought this was strict, but error-return, package-comments, if-return, etc. no longer fire.
Bug: the moment you provide any [rule.X] blocks, the built-in default rule set is replaced, not extended. Only the rules listed in the file are active. Fix: list every rule you want, including those you assumed were "always on":
[rule.var-naming]
[rule.exported]
[rule.package-comments]
[rule.error-return]
[rule.error-strings]
[rule.if-return]
[rule.indent-error-flow]
[rule.receiver-naming]
[rule.context-as-argument]
[rule.context-keys-type]
Bug 9 — CI script parses ndjson wrong¶
revive -formatter ndjson ./... > out.txt
ERRORS=$(grep '"Severity":"error"' out.txt | wc -l)
[ "$ERRORS" -gt 0 ] && exit 1
The job passes even when there are clear errors.
Bug: the script forgot -set_exit_status and assumed ndjson is just text — the field order or whitespace inside the JSON object can vary across revive versions, so substring matching is brittle. Also, revive exits 0 by default, so a downstream step might && past this whole block. Fix: use a real JSON parser and -set_exit_status:
revive -config revive.toml -formatter ndjson -set_exit_status ./... \
| jq -e 'select(.Severity == "error")' && exit 1
exit 0
Or even simpler — let -set_exit_status plus the right errorCode do the work:
Bug 10 — revive fires on // Code generated file¶
Bug: by default revive should skip generated files, but the header line is wrong — mockgen (and some custom generators) put it in the wrong column or include extra leading text, so revive's detection misses it. Fix: make sure the very first non-build-tag line matches the canonical regex // Code generated .* DO NOT EDIT\. exactly. Either fix the generator template, or -exclude the path:
Or use a file-level directive:
How to approach these¶
- Read the finding's rule name and look it up in the rule catalogue — most messages alone are not enough.
- If the rule does not match your context, fix the config (exclude, arguments, severity) before reaching for inline disables.
- When a config file is in play, remember it replaces the defaults — listing two rules turns off everything else.
- For CI parsing, use
ndjson+jqand-set_exit_status; never grep the human formatter. - For generated code, fix the generator's header first; excludes are second-best.