{"record":{"id":"d06258be46a0a0ee","repo":"hashicorp/nomad","slug":"root-should-be-an-object","errorCode":null,"errorMessage":"root should be an object","messagePattern":"root should be an object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugins/shared/cmd/launcher/command/device.go","lineNumber":233,"sourceCode":"\n\treturn nil\n}\n\nfunc hclConfigToAny(config []byte) (any, error) {\n\tif len(config) == 0 {\n\t\treturn map[string]any{}, nil\n\t}\n\n\t// Parse as we do in the jobspec parser\n\troot, err := hcl.Parse(string(config))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to hcl parse the config: %v\", err)\n\t}\n\n\t// Top-level item should be a list\n\tlist, ok := root.Node.(*ast.ObjectList)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"root should be an object\")\n\t}\n\n\tvar m map[string]any\n\tif err := hcl.DecodeObject(&m, list.Items[0]); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode object: %v\", err)\n\t}\n\n\treturn m[\"config\"], nil\n}\n\nfunc (c *Device) startRepl() error {\n\t// Start the output goroutine\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\tfingerprint := make(chan context.Context)\n\tstats := make(chan context.Context)\n\treserve := make(chan []string)\n\tgo c.replOutput(ctx, fingerprint, stats, reserve)","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/plugins/shared/cmd/launcher/command/device.go#L215-L251","documentation":"After parsing the config, hclConfigToAny expects the top-level HCL node to be an ast.ObjectList (a block-style document). If the parsed root is something else (e.g. a bare expression/object), the structure is invalid and this error is returned.","triggerScenarios":"Passing config whose top level is not a block, e.g. a bare value like {\"a\":1}-style JSON or a scalar expression instead of `config \"name\" { ... }` blocks.","commonSituations":"Supplying JSON config where HCL blocks are expected (JSON objects parse to non-ObjectList nodes); config file containing only a key/value with no enclosing block; concatenating the wrong file.","solutions":["Wrap the configuration in a proper top-level block, e.g. `config \"mock\" { ... }`.","Convert JSON config to HCL block syntax used by the device plugin.","Verify the launcher is reading the intended config file/section."],"exampleFix":"// before\n{ \"foo\": \"bar\" }\n// after\nconfig \"mock\" {\n  foo = \"bar\"\n}","handlingStrategy":"validation","validationCode":"// Ensure the config contains a top-level block before parsing\nif !strings.Contains(string(config), \"config \") {\n\treturn errors.New(\"device config must define a top-level `config` block\")\n}","typeGuard":null,"tryCatchPattern":"if err := setConfigErr; err != nil && err.Error() == \"root should be an object\" {\n\t// reject the config file and instruct the operator to use block syntax\n}","preventionTips":["Always wrap device settings in a `config \"name\" { ... }` block.","Reject JSON-looking config files at load time.","Keep an example config next to each device plugin.","Document the expected top-level structure in plugin docs."],"tags":["hcl","config","structure","device"],"backgroundTag":"hcl-parse-error","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}