{"record":{"id":"4b6f3790a135aa22","repo":"docker/cli","slug":"top-level-object-must-be-a-mapping","errorCode":null,"errorMessage":"top-level object must be a mapping","messagePattern":"top-level object must be a mapping","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/compose/loader/loader.go","lineNumber":69,"sourceCode":"\treturn volumespec.Parse(spec)\n}\n\n// WithDiscardEnvFiles sets the Options to discard the `env_file` section after resolving to\n// the `environment` section\nfunc WithDiscardEnvFiles(options *Options) {\n\toptions.discardEnvFiles = true\n}\n\n// ParseYAML reads the bytes from a file, parses the bytes into a mapping\n// structure, and returns it.\nfunc ParseYAML(source []byte) (map[string]any, error) {\n\tvar cfg any\n\tif err := yaml.Unmarshal(source, &cfg); err != nil {\n\t\treturn nil, err\n\t}\n\t_, ok := cfg.(map[string]any)\n\tif !ok {\n\t\treturn nil, errors.New(\"top-level object must be a mapping\")\n\t}\n\tconverted, err := convertToStringKeysRecursive(cfg, \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn converted.(map[string]any), nil\n}\n\n// Load reads a ConfigDetails and returns a fully loaded configuration\nfunc Load(configDetails types.ConfigDetails, opt ...func(*Options)) (*types.Config, error) {\n\tif len(configDetails.ConfigFiles) < 1 {\n\t\treturn nil, errors.New(\"no files specified\")\n\t}\n\n\toptions := &Options{\n\t\tInterpolate: &interp.Options{\n\t\t\tSubstitute:      template.Substitute,\n\t\t\tLookupValue:     configDetails.LookupEnv,","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/docker/cli/blob/4f84911bfe8811e9b028e4b1fee8e7510be79387/cli/compose/loader/loader.go#L51-L87","documentation":"Thrown by ParseYAML after it unmarshals the YAML bytes into a generic any and asserts the result is a map[string]any. Compose files must be a mapping at the top level (e.g. keyed by 'services:'); a YAML document whose root is a list, a scalar, or empty does not satisfy this and is rejected before any further parsing. This guards the contract that a compose file is an object of named sections, not a bare array or value.","triggerScenarios":"Calling loader.ParseYAML or loader.Load with a compose file whose first non-comment line is '- ' (a sequence) or a plain scalar like 'hello', or with a file that is empty/contains only comments after interpolation. Passing a fragment intended to be merged under an existing key rather than a full document also triggers it.","commonSituations":"A developer splits a compose file and accidentally submits a per-service snippet (a single map under a service name) as a standalone file. A YAML list of services produced by a templating tool or AI assistant. A file reduced to only comments or whitespace after variable substitution removed all content.","solutions":["Open the offending compose file and ensure the top-level is a mapping, e.g. start with 'services:' as the root key.","If you intended a list of services, restructure it as 'services:\\n  name: {...}' so the list becomes named service entries.","Run 'docker compose config' on the file to reproduce and get the exact file path in the error.","Check that interpolation did not strip the entire document; ensure variables referenced by the top-level keys resolve."],"exampleFix":"// before (broken - root is a list)\n- web:\n    image: nginx\n// after\nservices:\n  web:\n    image: nginx","handlingStrategy":"validation","validationCode":"// Validate a compose document is a top-level mapping before calling loader.Load.\nfunc isTopLevelMapping(b []byte) error {\n    var v any\n    if err := yaml.Unmarshal(b, &v); err != nil {\n        return fmt.Errorf(\"invalid yaml: %w\", err)\n    }\n    if v == nil {\n        return errors.New(\"compose file is empty\")\n    }\n    if _, ok := v.(map[string]any); !ok {\n        return errors.New(\"top-level yaml is not a mapping; expected keys like 'services:'\")\n    }\n    return nil\n}","typeGuard":"func isMappingDoc(b []byte) bool {\n    var v any\n    _ = yaml.Unmarshal(b, &v)\n    _, ok := v.(map[string]any)\n    return ok\n}","tryCatchPattern":null,"preventionTips":["Validate the file shape with 'docker compose config' before committing.","Never submit per-service snippets as standalone compose files.","Lint compose files in CI with a yaml parser asserting the root is an object."],"tags":["compose","yaml","config-parsing","validation"],"backgroundTag":null,"analyzedSha":"4f84911bfe8811e9b028e4b1fee8e7510be79387","analyzedAt":"2026-08-07T12:15:29.814Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}