{"record":{"id":"c0548e5a99943ee3","repo":"gastownhall/beads","slug":"failed-to-marshal-config-w","errorCode":null,"errorMessage":"failed to marshal config: %w","messagePattern":"failed to marshal config: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/config.go","lineNumber":657,"sourceCode":"\tconfigPath := v.ConfigFileUsed()\n\tif configPath == \"\" {\n\t\tconfigPath = filepath.Join(beadsDir, \"config.yaml\")\n\t\tv.SetConfigFile(configPath)\n\t}\n\n\t// Read existing file contents to avoid dumping all merged viper state\n\t// (defaults, env vars, overrides) into the config file.\n\texisting := make(map[string]interface{})\n\tif data, err := os.ReadFile(filepath.Clean(configPath)); err == nil {\n\t\t_ = yaml.Unmarshal(data, &existing)\n\t}\n\n\t// Set the single key using dot-path splitting for nested keys (e.g. \"routing.mode\").\n\tsetNestedKey(existing, key, value)\n\n\tout, err := yaml.Marshal(existing)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to marshal config: %w\", err)\n\t}\n\treturn os.WriteFile(configPath, out, 0o600)\n}\n\n// setNestedKey sets a value in a nested map using a dot-separated key path.\nfunc setNestedKey(m map[string]interface{}, key string, value interface{}) {\n\tparts := strings.SplitN(key, \".\", 2)\n\tif len(parts) == 1 {\n\t\tm[key] = value\n\t\treturn\n\t}\n\tsub, ok := m[parts[0]].(map[string]interface{})\n\tif !ok {\n\t\tsub = make(map[string]interface{})\n\t\tm[parts[0]] = sub\n\t}\n\tsetNestedKey(sub, parts[1], value)\n}","sourceCodeStart":639,"sourceCodeEnd":675,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/config/config.go#L639-L675","documentation":"SaveConfigValue reads the existing config into a map, applies the single key change via setNestedKey, then re-serializes with yaml.Marshal before writing. This error wraps a failure of yaml.Marshal, which is rare but possible when the existing config contains values that cannot be marshaled (e.g. unmarshalable types, invalid map keys, cyclic structures injected programmatically).","triggerScenarios":"The in-memory config map contains a value of a type gopkg.in/yaml cannot marshal — e.g. a func, channel, or a map with non-string/non-int keys — typically injected earlier via Set/SetDefault or by a corrupted read.","commonSituations":"A custom default or programmatically set value of an unsupported type ends up in the config tree; a third-party integration stores an exotic value under a config key.","solutions":["Inspect the config for values set with unusual types and convert them to strings, numbers, bools, slices, or maps before saving.","Log the wrapped yaml.Marshal error for the offending type; yaml errors name the unmarshalable value.","Reset the offending key to a plain value with Set before calling SaveConfigValue.","If caused by a bad SetDefault in code, fix the default to use a marshalable type."],"exampleFix":"// before\nv.Set(\"custom.handler\", someFunc) // func is not YAML-marshalable\n// after\nv.Set(\"custom.handler\", \"handlerName\") // store a serializable value","handlingStrategy":"try-catch","validationCode":"// keep values marshalable before saving:\nswitch v.(type) {\ncase string, bool, int, float64, []interface{}, map[string]interface{}, nil:\n    // ok\ndefault:\n    return fmt.Errorf(\"value for %s of type %T is not YAML-marshalable\", key, value)\n}","typeGuard":null,"tryCatchPattern":"if err := config.SaveConfigValue(key, value, beadsDir); err != nil {\n    if strings.Contains(err.Error(), \"failed to marshal config\") {\n        log.Warn(\"config contains unmarshalable value; saving as string\", \"key\", key)\n        return config.SaveConfigValue(key, fmt.Sprintf(\"%v\", value), beadsDir)\n    }\n    return err\n}","preventionTips":["Only store strings, numbers, booleans, lists, and maps in config keys.","Avoid passing funcs, channels, or pointers to exotic types into Set/SetDefault.","After programmatic Set calls, round-trip the config through Marshal in tests.","Review third-party integrations that inject config values for serializable types."],"tags":["config","yaml","serialization","marshal"],"backgroundTag":"yaml-marshal-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}