golangci/golangci-lint · error

failed to JSON marshal config linter settings

Error message

failed to JSON marshal config linter settings: %w

What it means

Inside computeConfigSalt, golangci-lint marshals cfg.Linters.Settings with yaml.Marshal; failure is wrapped (with a now-inaccurate "JSON marshal" message) as "failed to JSON marshal config linter settings". This error is propagated up as error 117's cause.

Solutions

  1. Find what mutates cfg.Linters.Settings (plugins, custom builds, wrappers) and ensure only standard config types are stored there.
  2. Use an unmodified golangci-lint release binary to rule out patched-build issues.
  3. Simplify .golangci.yml linter settings and re-run to confirm stock configs marshal fine.
  4. Upgrade golangci-lint; the misleading 'JSON' wording was fixed in later versions.

Example fix

// before (custom code)
cfg.Linters.Settings.Custom["x"] = someOpaqueValue
// after
cfg.Linters.Settings.Custom["x"] = config.CustomLinterSettings{Path: "./x.so"}
Defensive patterns

Strategy: try-catch

Validate before calling

// stock binary + stock config should always marshal
if _, err := yaml.Marshal(lintersSettingsStruct); err != nil {
	return fmt.Errorf("settings not serializable; custom mutation detected: %w", err)
}

Try / catch

if err := cmd.Run(); err != nil && strings.Contains(err.Error(), "failed to JSON marshal config linter settings") {
	log.Fatalf("non-serializable value injected into Linters.Settings (plugin/custom build?): %v", err)
}

Prevention

When it happens

Trigger: yaml.Marshal returns an error for the linter settings — practically only when the settings struct contains values the marshaller cannot represent, e.g. after programmatic mutation of the config in custom code or unsupported types injected by plugins.

Common situations: Forked/patched golangci-lint builds or custom formatters mutating Linters.Settings with non-serializable values; ABI/plugin mismatches where a custom linter writes incompatible data into settings.

Related errors


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

Appendix: source

Thrown at pkg/commands/run.go:688

	defer f.Close()

	h := sha256.New()
	if _, err := io.Copy(h, f); err != nil {
		return nil, err
	}

	return h.Sum(nil), nil
}

// computeConfigSalt computes configuration hash.
// We don't hash all config fields to reduce meaningless cache invalidations.
// At least, it has a huge impact on tests speed.
// Fields: `LintersSettings` and `Run.BuildTags`.
func computeConfigSalt(cfg *config.Config) ([]byte, error) {
	lintersSettingsBytes, err := yaml.Marshal(cfg.Linters.Settings)
	if err != nil {
		return nil, fmt.Errorf("failed to JSON marshal config linter settings: %w", err)
	}

	configData := bytes.NewBufferString("linters.settings=")
	configData.Write(lintersSettingsBytes)
	configData.WriteString("\nbuild-tags=%s" + strings.Join(cfg.Run.BuildTags, ","))

	h := sha256.New()
	if _, err := h.Write(configData.Bytes()); err != nil {
		return nil, err
	}

	return h.Sum(nil), nil
}

func computeGoModSalt() (string, error) {
	values, err := goenv.Get(context.Background(), goenv.GOMOD)
	if err != nil {
		return "", fmt.Errorf("failed to get goenv: %w", err)

View on GitHub (pinned to ed7a235d2d)