hashicorp/nomad · error

error parsing: root should be an object

Error message

error parsing: root should be an object

What it means

decodeHostVolume expects the parsed HCL input file to be an ObjectList (i.e. the top level of the HCL body is made of key/value blocks or attributes). When hclparse yields an *ast.File whose Node is not an *ast.ObjectList — for example an empty document or a bare literal — the command refuses to decode it and returns this error instead of panicking on the type assertion.

Source

Thrown at command/volume_create_host.go:216

		})
		if err != nil {
			return err
		}
		if vol.State == api.HostVolumeStateReady {
			c.Ui.Info(fmt.Sprintf("==> %s: Volume %q ready",
				formatTime(time.Now()), limit(vol.Name, opts.length)))
			return nil
		}
	}
}

func decodeHostVolume(input *ast.File) (*api.HostVolume, error) {
	var err error
	vol := &api.HostVolume{}

	list, ok := input.Node.(*ast.ObjectList)
	if !ok {
		return nil, fmt.Errorf("error parsing: root should be an object")
	}

	// Decode the full thing into a map[string]interface for ease
	var m map[string]any
	err = hcl.DecodeObject(&m, list)
	if err != nil {
		return nil, err
	}

	// Need to manually parse these fields/blocks
	delete(m, "capability")
	delete(m, "constraint")
	delete(m, "capacity")
	delete(m, "capacity_max")
	delete(m, "capacity_min")
	delete(m, "type")

	// Decode the rest

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Ensure the HCL file has at least one top-level `key = value` or `block { ... }` construct so the root parses as an object
  2. Verify the file path passed to the volume create command points to an HCL host-volume spec, not a job file, JSON array, or empty file
  3. Add a top-level attribute (e.g. `name = "..."`, `type = "host"`) to the spec
  4. Run `hclfmt` or the Nomad job validate/inspect equivalents to sanity-check the file parses to an object

Example fix

// before (empty or literal-only file)
"my-volume"

// after (proper HCL object root)
name = "my-volume"
type = "host"
capacity = "10GiB"
host_volume = "shared-csi-host-volume"
Defensive patterns

Strategy: validation

Validate before calling

func looksLikeHCLObject(input string) error {
	parsed, err := hcl.Parse(input)
	if err != nil {
		return err
	}
	if _, ok := parsed.Node.(*ast.ObjectList); !ok {
		return fmt.Errorf("volume spec root must be an HCL object (key = value / block { ... }), got %T", parsed.Node)
	}
	return nil
}
// call before handing the file to `nomad volume create`

Type guard

func isObjectList(f *ast.File) bool {
	_, ok := f.Node.(*ast.ObjectList)
	return ok
}

Try / catch

vol, err := decodeHostVolume(input)
if err != nil {
	if strings.Contains(err.Error(), "root should be an object") {
		return fmt.Errorf("volume file %s is empty or not an HCL object; check the file contents", path)
	}
	return err
}

Prevention

When it happens

Trigger: Calling `nomad volume create` (hostVolumeCreate/hostVolumeRegister) with an HCL volume spec whose root is not an object: an empty file, a file containing only a bare string/number, or non-HCL content that still parses (e.g. a JSON array at top level).

Common situations: Accidentally passing a YAML or JSON list file to the volume command; pointing the -volume flag at an empty file; copy/paste errors that drop all top-level keys; invoking the command with a path whose contents are binary or a lockfile.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/102ac72ae9a0b2ae. Report an issue: GitHub.