golangci/golangci-lint · error

panic occurred: %s

Error message

panic occurred: %s

What it means

golangci-lint recovers panics raised inside individual linters so one crashing linter does not abort the whole run. If the recovered value is not an errorutil.PanicError (i.e. a non-error value like a string or runtime error not wrapped by the library), the runner wraps it as a plain 'panic occurred: %s' error attributed to the linter name, and logs the goroutine stack via debug.Stack().

Source

Thrown at pkg/lint/runner.go:167

		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`.
		lintCtx.ClearTypesInPackages()
	}

	if err != nil {
		return nil, err

View on GitHub (pinned to ed7a235d2d)

Solutions

  1. Identify the linter from the wrapped message or log line 'Panic stack trace' and report/upgrade it (go.mod update or golangci-lint upgrade often fixes known linter panics)
  2. Disable the offending linter in .golangci.yml (linters.disable) or skip the triggering file via exclusions until fixed
  3. Run with --fix=false and a minimal reproduction file, then file an issue against the linter/golangci-lint with the stack trace
  4. Pin a golangci-lint version known to work with your Go toolchain version

Example fix

// before (.golangci.yml)
linters:
  enable-all: true
// after
linters:
  enable-all: true
  disable:
    - bodyclose # panics on generated code in vX.Y; re-enable after upgrade
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: cannot pre-validate linter internals, but pin compatible toolchain/linter versions
// go.mod: keep golangci-lint and its linters updated; test CI config on a small file first:
// golangci-lint run ./smallpkg/...

Type guard

var pe *errorutil.PanicError
if errors.As(err, &pe) {
	// linter panic: pe.Error() and pe.Stack() identify the culprit
}

Try / catch

if err != nil {
	var pe *errorutil.PanicError
	if errors.As(err, &pe) || strings.HasPrefix(err.Error(), "panic occurred:") {
		log.Printf("linter crashed; disabling it for this run: %v", err)
		err = nil // degrade gracefully instead of failing the pipeline
	}
}

Prevention

When it happens

Trigger: A linter's Run(ctx, lintCtx) panics with a raw non-PanicError value (e.g. nil map write, index out of range, panic("..."), panic(someString)) and the deferred recover in runLinter catches it, producing this error for that linter's result.

Common situations: Bugs in third-party/custom linters, linters incompatible with the Go version being analyzed, pathological input files (generated code, generics edge cases), out-of-memory-driven runtime panics during analysis.

Related errors


AI-assisted analysis of golangci/golangci-lint@ed7a235d2d (2026-09-02). Data as JSON: /api/errors/a0998f5d7ccf74cf. Report an issue: GitHub.