hashicorp/nomad · error
error parsing: root should be an object
Error message
error parsing: root should be an object
What it means
ParseConfigFile parses an agent config file twice: first via hcl.Decode into the config struct, then via hcl.Parse to hand-extract 'vault', 'consul', and 'keyring' blocks. This error is returned when the second hcl.Parse result's root AST node is not an *ast.ObjectList, meaning the file's top level is not an object/block structure HCL can filter.
Source
Thrown at command/agent/config_parse.go:85
Telemetry: &Telemetry{},
Vaults: []*config.VaultConfig{},
Reporting: config.DefaultReporting(),
}
err = hcl.Decode(c, buf.String())
if err != nil {
return nil, fmt.Errorf("failed to decode HCL file %s: %w", path, err)
}
// Re-parse the file to extract the multiple Vault configurations, which we
// need to parse by hand because we don't have a label on the block
root, err := hcl.Parse(buf.String())
if err != nil {
return nil, fmt.Errorf("failed to parse HCL file %s: %w", path, err)
}
list, ok := root.Node.(*ast.ObjectList)
if !ok {
return nil, fmt.Errorf("error parsing: root should be an object")
}
matches := list.Filter("vault")
if len(matches.Items) > 0 {
if err := parseVaults(c, matches); err != nil {
return nil, fmt.Errorf("error parsing 'vault': %w", err)
}
}
matches = list.Filter("consul")
if len(matches.Items) > 0 {
if err := parseConsuls(c, matches); err != nil {
return nil, fmt.Errorf("error parsing 'consul': %w", err)
}
}
matches = list.Filter("keyring")
if len(matches.Items) > 0 {
if err := parseKeyringConfigs(c, matches); err != nil {
return nil, fmt.Errorf("error parsing 'keyring': %w", err)View on GitHub (pinned to 482b49bf1a)
Solutions
- Open the config file and ensure the top level is an HCL object/block structure (e.g. `vault { ... }` or key = value at root), not a bare value or array
- If the file is JSON, wrap contents in a top-level object `{ ... }` instead of an array or scalar
- Verify the path passed to LoadConfig points to a real HCL config file, not a directory or binary
- Re-save the file as UTF-8 without a BOM, which can confuse the HCL parser
Example fix
// before (config.hcl)
["vault", "consul"]
// after
vault {}
consul {} Defensive patterns
Strategy: validation
Validate before calling
func validateHCLRoot(path string) error {
data, err := os.ReadFile(path)
if err != nil {
return err
}
trimmed := bytes.TrimSpace(data)
if len(trimmed) == 0 {
return fmt.Errorf("%s is empty", path)
}
if trimmed[0] == '[' || trimmed[0] == '"' || trimmed[0] == '-' {
return fmt.Errorf("%s: top-level value is not an HCL object", path)
}
return nil
} Type guard
func isHCLObjectNode(n ast.Node) bool {
_, ok := n.(*ast.ObjectList)
return ok
} Try / catch
cfg, err := ParseConfigFile(path)
if err != nil {
if strings.Contains(err.Error(), "root should be an object") {
return fmt.Errorf("config %s has no top-level object; check file contents", path)
}
return err
} Prevention
- Keep config files as HCL objects/blocks at the top level; never a bare value or array
- Lint config files with an HCL parser in CI before deployment
- Verify file paths point to files, not directories or binaries
- Re-save files as UTF-8 without BOM
When it happens
Trigger: Calling ParseConfigFile (directly or via LoadConfig/LoadConfigDir) on an HCL file whose top-level node parses to something other than an ObjectList (e.g. a bare scalar or list at the top level, or a malformed/empty file producing an unexpected AST root).
Common situations: Config file containing only a JSON array, a bare value like "abc", or a file corrupted/truncated so hcl.Parse returns a non-object root; also seen when a non-HCL file is passed as a config path.
Related errors
- error parsing 'vault': %w
- error parsing 'consul': %w
- error parsing 'keyring': %w
- should be an object
- failed to decode key %q to string
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/9c8a93357558c39a.
Report an issue: GitHub.