{"record":{"id":"f368d6654fd9b3c7","repo":"router-for-me/CLIProxyAPI","slug":"expected-root-mapping-node","errorCode":null,"errorMessage":"expected root mapping node","messagePattern":"expected root mapping node","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"internal/config/config_yaml.go","lineNumber":30,"sourceCode":"// SaveConfigPreserveComments writes the config back to YAML while preserving existing comments\n// and key ordering by loading the original file into a yaml.Node tree and updating values in-place.\nfunc SaveConfigPreserveComments(configFile string, cfg *Config) error {\n\tpersistCfg := cfg\n\t// Load original YAML as a node tree to preserve comments and ordering.\n\tdata, err := os.ReadFile(configFile)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar original yaml.Node\n\tif err = yaml.Unmarshal(data, &original); err != nil {\n\t\treturn err\n\t}\n\tif original.Kind != yaml.DocumentNode || len(original.Content) == 0 {\n\t\treturn fmt.Errorf(\"invalid yaml document structure\")\n\t}\n\tif original.Content[0] == nil || original.Content[0].Kind != yaml.MappingNode {\n\t\treturn fmt.Errorf(\"expected root mapping node\")\n\t}\n\n\t// Marshal the current cfg to YAML, then unmarshal to a yaml.Node we can merge from.\n\trendered, err := yaml.Marshal(persistCfg)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar generated yaml.Node\n\tif err = yaml.Unmarshal(rendered, &generated); err != nil {\n\t\treturn err\n\t}\n\tif generated.Kind != yaml.DocumentNode || len(generated.Content) == 0 || generated.Content[0] == nil {\n\t\treturn fmt.Errorf(\"invalid generated yaml structure\")\n\t}\n\tif generated.Content[0].Kind != yaml.MappingNode {\n\t\treturn fmt.Errorf(\"expected generated root mapping node\")\n\t}\n","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/config/config_yaml.go#L12-L48","documentation":"Thrown by the config persistence path when config.yaml's root node is not a mapping. The sanitizer merges sanitized config values into the original document tree to preserve comments and ordering; that merge requires the document root to be a yaml.MappingNode. A sequence, scalar, or alias at the root makes lossless merge impossible.","triggerScenarios":"config.yaml whose top-level structure is a YAML list ('- a\\n- b'), a bare scalar ('8317'), or a null root ('--- ~'), then a management-API config save or other persistence flow runs.","commonSituations":"Reusing a file that was meant as a data list (e.g. a models list) as the main config; hand-merging configs and accidentally indenting everything under a list item; a misconfigured symlink pointing at an auth-file list instead of config.yaml.","solutions":["Rewrite config.yaml so the top level is a mapping of key/value pairs (port:, auth-dir:, etc.).","Move list content under a proper key, e.g. 'models:\\n  - a\\n  - b'.","Start from config.example.yaml which already has a mapping root.","Confirm you are pointing --config at the right file (it may be an auth JSON list or other data file)."],"exampleFix":"# before: config.yaml\n- port: 8317\n- auth-dir: ./auths\n\n# after: config.yaml\nport: 8317\nauth-dir: ./auths","handlingStrategy":"validation","validationCode":"// Go: assert the config file root is a mapping before persisting.\nfunc configRootIsMapping(path string) (bool, error) {\n    data, err := os.ReadFile(path)\n    if err != nil {\n        return false, err\n    }\n    var doc yaml.Node\n    if err := yaml.Unmarshal(data, &doc); err != nil {\n        return false, err\n    }\n    return doc.Kind == yaml.DocumentNode && len(doc.Content) > 0 &&\n        doc.Content[0] != nil && doc.Content[0].Kind == yaml.MappingNode, nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep the top level of config.yaml strictly key: value pairs.","Put list-shaped data under a named key, never at the document root.","Double-check --config points at the main config, not a data or auth file."],"tags":["config","yaml","persistence","structure"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}