golangci/golangci-lint · error

can't unmarshal config by viper (flags, file): %w

Error message

can't unmarshal config by viper (flags, file): %w

What it means

After successfully reading a config file, the loader unmarshals the merged view (flags + file) into the config struct with a custom decoder hook. A type or shape mismatch between the merged configuration and the struct produces this wrapped error.

Source

Thrown at pkg/config/base_loader.go:178

			err = l.viper.Unmarshal(l.cfg, customDecoderHook())
			if err != nil {
				return fmt.Errorf("can't unmarshal config by viper (flags): %w", err)
			}

			return nil
		}

		return fmt.Errorf("can't read viper config: %w", err)
	}

	err := l.setConfigDir()
	if err != nil {
		return err
	}

	// Load configuration from all sources (flags, file).
	if err := l.viper.Unmarshal(l.cfg, customDecoderHook()); err != nil {
		return fmt.Errorf("can't unmarshal config by viper (flags, file): %w", err)
	}

	if l.cfg.IsInternalTest() { // just for testing purposes: to detect config file usage
		_, _ = fmt.Fprintln(logutils.StdOut, "test")
		os.Exit(exitcodes.Success)
	}

	return nil
}

func (l *BaseLoader) setConfigDir() error {
	usedConfigFile := l.viper.ConfigFileUsed()
	if usedConfigFile == "" {
		return nil
	}

	if usedConfigFile == os.Stdin.Name() {
		usedConfigFile = ""

View on GitHub (pinned to ed7a235d2d)

Solutions

  1. Read the wrapped error: viper names the field that failed to decode and the found/expected types
  2. Fix the value's type/shape in the config file (lists need `- item` entries, ints unquoted, etc.)
  3. Check the docs for the current key names — deprecated keys may need migration
  4. Bisect by commenting out config sections until unmarshal succeeds

Example fix

# before
issues:
  exclude-dirs: vendor
# after
issues:
  exclude-dirs:
    - vendor
Defensive patterns

Strategy: validation

Validate before calling

var raw map[string]any
data, _ := os.ReadFile(".golangci.yml")
if err := yaml.Unmarshal(data, &raw); err != nil {
	log.Fatal(err)
}
if v, ok := raw["issues"].(map[string]any); ok {
	if ed, ok := v["exclude-dirs"].(string); ok {
		log.Fatalf("exclude-dirs must be a list, got string: %q", ed)
	}
}

Prevention

When it happens

Trigger: A config file (or flag) sets a key whose value type doesn't match the config struct field — e.g. a string where a []string is expected, a nested map where a scalar is expected, or a deprecated/renamed key with the wrong shape.

Common situations: Upgrading golangci-lint after renaming/moving config keys; copy-pasting config from another tool; putting a single value where a list is required; wrong nesting level (e.g. option under `linters` instead of `linters-settings`).

Related errors


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