{"record":{"id":"b7d597df72464d84","repo":"caddyserver/caddy","slug":"indexing-config-v","errorCode":null,"errorMessage":"indexing config: %v","messagePattern":"indexing config: (.+?)","errorType":"http","errorClass":"APIError","httpStatus":400,"severity":"error","filePath":"caddy.go","lineNumber":241,"sourceCode":"\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)\n\t\t\t}\n\t\t\trawCfg[rawConfigKey] = oldCfg\n\t\t} else {\n\t\t\trawCfg[rawConfigKey] = nil\n\t\t}\n\t\treturn APIError{\n\t\t\tHTTPStatus: http.StatusBadRequest,\n\t\t\tErr:        fmt.Errorf(\"indexing config: %v\", err),\n\t\t}\n\t}\n\n\t// load this new config; if it fails, we need to revert to\n\t// our old representation of caddy's actual config\n\terr = unsyncedDecodeAndRun(newCfg, true)\n\tif err != nil {\n\t\tif len(rawCfgJSON) > 0 {\n\t\t\t// restore old config state to keep it consistent\n\t\t\t// with what caddy is still running; we need to\n\t\t\t// unmarshal it again because it's likely that\n\t\t\t// pointers deep in our rawCfg map were modified\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)\n\t\t\t}\n\t\t\trawCfg[rawConfigKey] = oldCfg","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/caddy.go#L223-L259","documentation":"changeConfig walks the new config tree with indexConfigObjects to register every '@id' field so /id/... admin paths can address config objects. Any object whose '@id' is not a string or number, or any duplicate '@id', aborts the change with this APIError (HTTP 400). On this path Caddy first restores rawCfg from the last good encoded copy, so the running config is unaffected.","triggerScenarios":"POSTing/PUTing a JSON config where an '@id' value is an object, array, boolean, or null; assigning the same '@id' string to two objects; PATCHing /config/ subtrees that introduce colliding IDs. Does not apply to Caddyfile-adapted configs unless they emit @id.","commonSituations":"Hand-written JSON configs with '@id': true or nested objects; generated configs where a template reuses an id; merging config fragments that each carry '@id': 'my-app'.","solutions":["Make every '@id' a unique string (or number) — e.g. \"@id\": \"srv0-tls\".","Search the submitted JSON for duplicate '\"@id\"' keys sharing the same value and rename them.","Remove '@id' fields you do not need for /id/ addressing."],"exampleFix":"// before\n{ \"@id\": true, \"apps\": { ... } }\n\n// after\n{ \"@id\": \"main\", \"apps\": { ... } }","handlingStrategy":"validation","validationCode":"// Before submitting, check every @id is string/number and unique.\nfunc checkIDs(node any, seen map[string]string, path string) error {\n    m, ok := node.(map[string]any)\n    if !ok { return nil }\n    if id, ok := m[\"@id\"]; ok {\n        s, ok := id.(string)\n        if !ok { _, ok = id.(float64) }\n        if !ok { return fmt.Errorf(\"%s: @id not string/number\", path) }\n        if _, dup := seen[fmt.Sprint(id)]; dup { return fmt.Errorf(\"duplicate @id %v at %s\", id, path) }\n        seen[fmt.Sprint(id)] = path\n    }\n    for k, v := range m { if err := checkIDs(v, seen, path+\"/\"+k); err != nil { return err } }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use only unique string @id values.","Lint generated configs for duplicate or non-scalar @id before POSTing.","Prefer caddy validate on the JSON config before loading it via the API."],"tags":["caddy","admin-api","config","id-index","validation"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}