golangci/golangci-lint · error

failed to JSON marshal config linter settings: %w

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.

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)

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.