{"record":{"id":"6f7802859fbfa980","repo":"caddyserver/caddy","slug":"encoding-new-config-v","errorCode":null,"errorMessage":"encoding new config: %v","messagePattern":"encoding new config: (.+?)","errorType":"http","errorClass":"APIError","httpStatus":400,"severity":"error","filePath":"caddy.go","lineNumber":215,"sourceCode":"\t\tif hex.EncodeToString(hash.Sum(nil)) != parts[1] {\n\t\t\treturn APIError{\n\t\t\t\tHTTPStatus: http.StatusPreconditionFailed,\n\t\t\t\tErr:        fmt.Errorf(\"If-Match header did not match current config hash\"),\n\t\t\t}\n\t\t}\n\t}\n\n\terr := unsyncedConfigAccess(method, path, input, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// the mutation is complete, so encode the entire config as JSON\n\tnewCfg, err := json.Marshal(rawCfg[rawConfigKey])\n\tif err != nil {\n\t\treturn APIError{\n\t\t\tHTTPStatus: http.StatusBadRequest,\n\t\t\tErr:        fmt.Errorf(\"encoding new config: %v\", err),\n\t\t}\n\t}\n\n\t// if nothing changed, no need to do a whole reload unless the client forces it\n\tif !forceReload && bytes.Equal(rawCfgJSON, newCfg) {\n\t\tLog().Info(\"config is unchanged\")\n\t\treturn errSameConfig\n\t}\n\n\t// find any IDs in this config and index them\n\tidx := make(map[string]string)\n\terr = indexConfigObjects(rawCfg[rawConfigKey], \"/\"+rawConfigKey, idx)\n\tif err != nil {\n\t\tif len(rawCfgJSON) > 0 {\n\t\t\tvar oldCfg any\n\t\t\terr2 := json.Unmarshal(rawCfgJSON, &oldCfg)\n\t\t\tif err2 != nil {\n\t\t\t\terr = fmt.Errorf(\"%v; additionally, restoring old config: %v\", err, err2)","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/caddy.go#L197-L233","documentation":"After a successful mutation of the in-memory raw config, changeConfig re-encodes the whole config with json.Marshal. If any value in the raw config map cannot be represented as JSON (unsupported type injected by a module or plugin, NaN/Inf float, circular structure), marshaling fails and the request aborts with an APIError (HTTP 400) wrapping 'encoding new config'. The raw config is left mutated but not loaded.","triggerScenarios":"A plugin or custom admin handler inserting non-JSON-serializable values (funcs, channels, NaN) into rawCfg before changeConfig re-encodes; extremely rare via stock Caddy because rawCfg originates from a JSON decode. JSON config paths (/config/, /id/) that programmatically set exotic values.","commonSituations":"Third-party modules hooking config storage; patched/forked Caddy builds; almost never seen with unmodified Caddy and file-based configs. When it does occur the culprit is usually a recently added custom module.","solutions":["Inspect the wrapped %v — it names the offending type/field that json.Marshal choked on.","Remove or fix the module/plugin that injects non-serializable values into the config tree.","If you control the code, store only JSON-compatible data (strings, numbers, bools, maps, slices) in raw config paths.","Restart from a known-good config file (caddy run --config) to clear the mutated in-memory state."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"err := changeConfig(...) // or admin POST\nif err != nil {\n    if apiErr, ok := err.(caddy.APIError); ok && strings.Contains(apiErr.Error(), \"encoding new config\") {\n        // in-memory raw config mutated but not loaded: restart from file\n        log.Fatal(\"config serialization failed; restart from known-good file\")\n    }\n}","preventionTips":["Never inject non-JSON values (funcs, NaN, cycles) into raw config structures.","Keep custom modules' stored config data strictly JSON-typed.","Always keep the canonical config on disk so a restart can recover state."],"tags":["caddy","admin-api","json","serialization","plugins"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}