{"record":{"id":"c8ae0508bf353c19","repo":"golangci/golangci-lint","slug":"can-t-unmarshal-config-by-viper-flags-file-w","errorCode":null,"errorMessage":"can't unmarshal config by viper (flags, file): %w","messagePattern":"can't unmarshal config by viper \\(flags, file\\): %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/base_loader.go","lineNumber":178,"sourceCode":"\t\t\terr = l.viper.Unmarshal(l.cfg, customDecoderHook())\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"can't unmarshal config by viper (flags): %w\", err)\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}\n\n\t\treturn fmt.Errorf(\"can't read viper config: %w\", err)\n\t}\n\n\terr := l.setConfigDir()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Load configuration from all sources (flags, file).\n\tif err := l.viper.Unmarshal(l.cfg, customDecoderHook()); err != nil {\n\t\treturn fmt.Errorf(\"can't unmarshal config by viper (flags, file): %w\", err)\n\t}\n\n\tif l.cfg.IsInternalTest() { // just for testing purposes: to detect config file usage\n\t\t_, _ = fmt.Fprintln(logutils.StdOut, \"test\")\n\t\tos.Exit(exitcodes.Success)\n\t}\n\n\treturn nil\n}\n\nfunc (l *BaseLoader) setConfigDir() error {\n\tusedConfigFile := l.viper.ConfigFileUsed()\n\tif usedConfigFile == \"\" {\n\t\treturn nil\n\t}\n\n\tif usedConfigFile == os.Stdin.Name() {\n\t\tusedConfigFile = \"\"","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/golangci/golangci-lint/blob/ed7a235d2d771152056fdc142a9855567c5796a9/pkg/config/base_loader.go#L160-L196","documentation":"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.","triggerScenarios":"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.","commonSituations":"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`).","solutions":["Read the wrapped error: viper names the field that failed to decode and the found/expected types","Fix the value's type/shape in the config file (lists need `- item` entries, ints unquoted, etc.)","Check the docs for the current key names — deprecated keys may need migration","Bisect by commenting out config sections until unmarshal succeeds"],"exampleFix":"# before\nissues:\n  exclude-dirs: vendor\n# after\nissues:\n  exclude-dirs:\n    - vendor","handlingStrategy":"validation","validationCode":"var raw map[string]any\ndata, _ := os.ReadFile(\".golangci.yml\")\nif err := yaml.Unmarshal(data, &raw); err != nil {\n\tlog.Fatal(err)\n}\nif v, ok := raw[\"issues\"].(map[string]any); ok {\n\tif ed, ok := v[\"exclude-dirs\"].(string); ok {\n\t\tlog.Fatalf(\"exclude-dirs must be a list, got string: %q\", ed)\n\t}\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["After upgrading golangci-lint, migrate renamed/deprecated config keys","Ensure list-typed options are YAML sequences, not scalars","Verify nesting: linter options go under linters-settings, exclusions under issues/linters.exclusions","Comment out config sections and bisect when unmarshal errors appear"],"tags":["golangci-lint","config","viper","unmarshal"],"backgroundTag":"config-unmarshal-failed","analyzedSha":"ed7a235d2d771152056fdc142a9855567c5796a9","analyzedAt":"2026-09-02T18:47:33.865Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}