{"record":{"id":"da2d84514d2fd89c","repo":"sipeed/picoclaw","slug":"failed-to-serialize-config-w","errorCode":null,"errorMessage":"failed to serialize config: %w","messagePattern":"failed to serialize config: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/picoclaw/internal/mcp/helpers.go","lineNumber":115,"sourceCode":"\nfunc loadConfig() (*config.Config, error) {\n\tcfg, err := config.LoadConfig(internal.GetConfigPath())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to load config: %w\", err)\n\t}\n\treturn cfg, nil\n}\n\nfunc saveValidatedConfig(cfg *config.Config) error {\n\tif cfg == nil {\n\t\treturn fmt.Errorf(\"config is nil\")\n\t}\n\n\tnormalizedCfg := normalizedConfigForSave(cfg)\n\n\tdata, err := json.Marshal(normalizedCfg)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to serialize config: %w\", err)\n\t}\n\n\tif err := validateConfigDocument(data); err != nil {\n\t\treturn err\n\t}\n\n\tif err := config.SaveConfig(internal.GetConfigPath(), normalizedCfg); err != nil {\n\t\treturn fmt.Errorf(\"failed to save config: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc normalizedConfigForSave(cfg *config.Config) *config.Config {\n\tclone := *cfg\n\tif cfg.Tools.MCP.Servers == nil {\n\t\treturn &clone\n\t}","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/cmd/picoclaw/internal/mcp/helpers.go#L97-L133","documentation":"json.Marshal of the normalized config failed (helpers.go:113-116). The config structs are plain data, so encoding/json only fails if a field type it cannot handle (chan, func, unsafe cyclic pointers) was introduced into config.Config — effectively a code regression in pkg/config, not a user-input problem.","triggerScenarios":"Someone adds a func or channel field to Config/MCPConfig/MCPServerConfig; a cyclic reference is created between config types.","commonSituations":"Refactors of pkg/config that attach live objects (loggers, clients) instead of serializable settings.","solutions":["Revert or redesign the offending field: keep config types to strings, numbers, bools, slices, and maps","If a live object is needed, mark it json:\"-\" so it is excluded from serialization"],"exampleFix":"// before\ntype MCPServerConfig struct {\n\tClient *mcp.Client `json:\"client\"` // cyclic/non-serializable\n}\n// after\ntype MCPServerConfig struct {\n\tClient *mcp.Client `json:\"-\"` // excluded from config serialization\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := saveValidatedConfig(cfg); err != nil {\n\tif strings.Contains(err.Error(), \"failed to serialize config\") {\n\t\t// internal: a config field type broke encoding/json;\n\t\t// fail loudly and file a bug rather than retrying\n\t\treturn fmt.Errorf(\"config struct no longer JSON-serializable: %w\", err)\n\t}\n\treturn err\n}","preventionTips":["Keep config structs limited to JSON-native types; mark live objects json:\"-\"","Add a round-trip unit test (Marshal then Unmarshal) for config types"],"tags":["go","json","serialization","internal"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}