{"record":{"id":"c99e1302af73def4","repo":"router-for-me/CLIProxyAPI","slug":"expected-generated-root-mapping-node","errorCode":null,"errorMessage":"expected generated root mapping node","messagePattern":"expected generated root mapping node","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"internal/config/config_yaml.go","lineNumber":46,"sourceCode":"\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\n\t// Remove deprecated sections before merging back the sanitized config.\n\tremoveLegacyAuthBlock(original.Content[0])\n\tremoveLegacyOpenAICompatAPIKeys(original.Content[0])\n\tremoveRemovedIntegrationKeys(original.Content[0])\n\tremoveLegacyGenerativeLanguageKeys(original.Content[0])\n\n\tpruneMappingToGeneratedKeys(original.Content[0], generated.Content[0], \"oauth-excluded-models\")\n\tpruneMappingToGeneratedKeys(original.Content[0], generated.Content[0], \"oauth-model-alias\")\n\tpruneMappingToGeneratedKeys(original.Content[0], generated.Content[0], \"plugins\", \"configs\")\n\n\t// Merge generated into original in-place, preserving comments/order of existing nodes.\n\tmergeMappingPreserve(original.Content[0], generated.Content[0])\n\tnormalizeCollectionNodeStyles(original.Content[0])\n\n\t// Write back.\n\tf, err := os.Create(configFile)","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/config/config_yaml.go#L28-L64","documentation":"Companion invariant to 'invalid generated yaml structure': after re-parsing the marshaled sanitized config, its root content node must be a MappingNode so the sanitizer can merge it key-by-key into the original document. yaml.Marshal of the Config struct always produces a mapping root, so this error indicates a build/library anomaly (custom marshaller returning a sequence or scalar) rather than a user-editable config problem.","triggerScenarios":"Only if a Config sub-type has a custom MarshalYAML that emits a non-mapping root document (e.g. returns a sequence node), or a modified yaml.v3 dependency changes document emission.","commonSituations":"Custom forks adding exotic marshal behaviour to the top-level config; vendored dependency drift; not seen with the shipped code.","solutions":["Audit any custom MarshalYAML methods added to Config or its embedded structs; make them return mappings.","Revert local modifications to internal/config and rebuild.","Restore the pinned yaml.v3 dependency ('go mod tidy', rebuild).","Report upstream with reproduction details if it occurs on unmodified code."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep Config struct marshalling unmodified in forks.","Any custom MarshalYAML must return a mapping node.","Report occurrences upstream — this invariant should not fire in stock builds."],"tags":["config","yaml","internal","invariant"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}