golangci/golangci-lint · error
%s: %w
Error message
%s: %w
What it means
runLinterSafe wraps linter execution in a recover() defer. If a linter panics with an *errorutil.PanicError, it is converted to "<lintername>: <panic error>" and logged with its stack trace; other panic values become a generic "panic occurred" error. This prevents one linter's crash from aborting the whole golangci-lint run.
Source
Thrown at pkg/lint/runner.go:162
lintErrors = errors.Join(lintErrors, fmt.Errorf("can't run linter %s", lc.Linter.Name()), err)
r.Log.Warnf("Can't run linter %s: %v", lc.Linter.Name(), err)
continue
}
issues = append(issues, linterIssues...)
}
return r.processLintResults(issues), lintErrors
}
func (r *Runner) runLinterSafe(ctx context.Context, lintCtx *linter.Context,
lc *linter.Config,
) (ret []*result.Issue, err error) {
defer func() {
if panicData := recover(); panicData != nil {
if pe, ok := panicData.(*errorutil.PanicError); ok {
err = fmt.Errorf("%s: %w", lc.Name(), pe)
// Don't print stacktrace from goroutines twice
r.Log.Errorf("Panic: %s: %s", pe, pe.Stack())
} else {
err = fmt.Errorf("panic occurred: %s", panicData)
r.Log.Errorf("Panic stack trace: %s", debug.Stack())
}
}
}()
issues, err := lc.Linter.Run(ctx, lintCtx)
if lc.DoesChangeTypes {
// Packages in lintCtx might be dirty due to the last analysis,
// which affects to the next analysis.
// To avoid this issue, we clear type information from the packages.
// See https://github.com/golangci/golangci-lint/pull/944.
// Currently, DoesChangeTypes is true only for `unused`.View on GitHub (pinned to ed7a235d2d)
Solutions
- Identify the linter from the message prefix and check its issue tracker for known panics on your code
- Upgrade golangci-lint (and bundled linters) to the latest patch release where the panic may be fixed
- Exclude the triggering file/pattern via `linters.exclusions.rules` or `linters.exclusions.paths` as a workaround
- Reduce to a minimal reproducing file and report the panic upstream with the logged stack trace
Example fix
// before (.golangci.yml)
linters:
enable:
- buggedlinter # panics on generated code
// after
linters:
enable:
- buggedlinter
exclusions:
rules:
- path: '.*_gen\.go'
linters:
- buggedlinter Defensive patterns
Strategy: fallback
Validate before calling
// Pre-flight: run the linter standalone on a small sample to detect panicking versions: // golangci-lint run --enable-only <name> ./... || echo "linter panics; pin or exclude"
Try / catch
defer func() {
if p := recover(); p != nil {
if pe, ok := p.(*errorutil.PanicError); ok {
err = fmt.Errorf("%s: %w", lc.Name(), pe)
} else {
err = fmt.Errorf("panic occurred: %s", p)
}
}
}() Prevention
- Pin golangci-lint to a stable release and upgrade promptly for linter panic fixes
- Check linter issue trackers for panics before enabling pre-release linters
- Exclude known-trigger files (generated code, exotic generics) via exclusion rules
- Always capture and report the logged stack trace when filing upstream bugs
When it happens
Trigger: A bug inside a linter (index out of range, nil dereference) triggered while analyzing specific code patterns, concurrent map access, or a linter analysis pass hitting an unexpected AST/type state. The panic is recovered in runLinterSafe and surfaced as this wrapped error.
Common situations: Linter bugs on unusual code (generics edge cases, exotic build tags); incompatible linter plugin versions; known upstream bugs in a linter release that are fixed in a later version.
Related errors
- can't run linter %s
- the configuration contains invalid elements
- unsupported configuration format
- the configuration contains invalid elements
- parallel golangci-lint is running
AI-assisted analysis of golangci/golangci-lint@ed7a235d2d (2026-09-02).
Data as JSON: /api/errors/bf0dc4d7026b56a8.
Report an issue: GitHub.