{"record":{"id":"facef7c5e03e8432","repo":"golangci/golangci-lint","slug":"can-t-unmarshal-config-by-viper-flags-w","errorCode":null,"errorMessage":"can't unmarshal config by viper (flags): %w","messagePattern":"can't unmarshal config by viper \\(flags\\): %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/base_loader.go","lineNumber":162,"sourceCode":"\t}\n\n\t// find home directory for global config\n\tif home, err := homedir.Dir(); err != nil {\n\t\tl.log.Warnf(\"Can't get user's home directory: %v\", err)\n\t} else if !slices.Contains(searchPaths, home) {\n\t\tsearchPaths = append(searchPaths, home)\n\t}\n\n\treturn searchPaths\n}\n\nfunc (l *BaseLoader) parseConfig() error {\n\tif err := l.viper.ReadInConfig(); err != nil {\n\t\tif _, ok := errors.AsType[viper.ConfigFileNotFoundError](err); ok {\n\t\t\t// Load configuration from flags only.\n\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","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/golangci/golangci-lint/blob/ed7a235d2d771152056fdc142a9855567c5796a9/pkg/config/base_loader.go#L144-L180","documentation":"When viper cannot find any config file (viper.ConfigFileNotFoundError), the loader falls back to unmarshaling configuration from flags only into the settings struct. If viper.Unmarshal fails on the flag-only view, this error is returned and loading aborts.","triggerScenarios":"No config file found (or config search disabled) AND a flag-set value cannot be decoded into the config struct via the custom decoder hook — typically a flag value of the wrong type/format.","commonSituations":"Passing values with wrong types via flags (e.g. non-integer for a duration/int option); typo'd flag mapped onto a struct field with an incompatible type; malformed map/slice syntax in flag values.","solutions":["Check the wrapped error for the offending field and fix the flag value type/format","Validate flags against `golangci-lint run --help` for expected value formats","Remove or correct the recently added flag(s) to isolate the culprit","Use a config file instead of many flags so values are type-checked in one place"],"exampleFix":"// before\ngolangci-lint run --timeout=abc\n// after\ngolangci-lint run --timeout=5m","handlingStrategy":"validation","validationCode":"// Validate flag-only values before running\nargs := []string{\"--timeout=5m\", \"--out-format=line-number\"}\nfor _, a := range args {\n\tif strings.Contains(a, \"timeout=\") {\n\t\tif _, err := time.ParseDuration(strings.TrimPrefix(a, \"--timeout=\")); err != nil {\n\t\t\tlog.Fatalf(\"bad flag value: %s\", a)\n\t\t}\n\t}\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use correct value types for flags (durations as 5m, ints unquoted)","Introduce new flags one at a time to isolate bad values","Prefer a config file over long flag lists for complex settings","Read the wrapped error: viper names the failing field"],"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"}