{"record":{"id":"ceb788cc5bd7df5d","repo":"router-for-me/CLIProxyAPI","slug":"invalid-yaml-document-structure","errorCode":null,"errorMessage":"invalid yaml document structure","messagePattern":"invalid yaml document structure","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"internal/config/config_yaml.go","lineNumber":27,"sourceCode":"\t\"gopkg.in/yaml.v3\"\n)\n\n// 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 {","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/config/config_yaml.go#L9-L45","documentation":"Thrown by the config persistence path (config_yaml.go) when the on-disk config.yaml parses as YAML but is not a well-formed single YAML document with content. yaml.Unmarshal succeeded, but the resulting root node is not a DocumentNode or has zero Content nodes (e.g. a completely empty file or a null document). The sanitizer needs a document tree to merge sanitized values back while preserving comments.","triggerScenarios":"config.yaml exists but is empty (0 bytes), contains only comments/whitespace, contains only '---' or 'null', or has multiple documents where unmarshal takes a degenerate root. Triggered when the management API or config save flow persists a sanitized config.","commonSituations":"Fresh deployment where config.yaml was created empty by a container volume mount or a truncated write; a crashed previous save leaving a stub file; a config template that is all comments; kubernetes ConfigMount of an empty file over config.yaml.","solutions":["Put at least one real mapping entry into config.yaml (e.g. 'port: 8317') or restore it from config.example.yaml.","If the file is empty by accident, copy config.example.yaml to config.yaml and re-edit.","Check the file was not truncated by a previous crash or a half-completed atomic write.","Verify with 'cat config.yaml' that the file contains a top-level mapping, not just comments or '---'."],"exampleFix":"# before: config.yaml is empty or only comments\n# (file exists, zero mappings)\n\n# after\nport: 8317\nauth-dir: ./auths","handlingStrategy":"validation","validationCode":"#!/bin/sh\n# config.yaml must contain at least one top-level mapping entry.\nif [ ! -s config.yaml ] || ! grep -qE '^[A-Za-z0-9_-]+:' config.yaml; then\n  echo \"config.yaml is empty or lacks a top-level mapping\" >&2\n  exit 1\nfi","typeGuard":null,"tryCatchPattern":"if err := persistConfig(cfg, \"config.yaml\"); err != nil {\n    if strings.Contains(err.Error(), \"invalid yaml document structure\") {\n        log.Errorf(\"config.yaml is empty or has no document content; restore it from config.example.yaml\")\n    }\n    return err\n}","preventionTips":["Never mount an empty file over config.yaml; seed it from config.example.yaml.","Keep config.yaml under version control so truncation is recoverable.","Persist configs atomically (write temp file + rename) to avoid empty stubs after crashes."],"tags":["config","yaml","persistence","filesystem"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}