hashicorp/nomad · error

Invalid or duplicate policy keys: %v

Error message

Invalid or duplicate policy keys: %v

What it means

In strict mode (PolicyParseStrict, used when creating/updating ACL policies), acl.Parse (acl/policy.go:672) rejects policies whose HCL contained keys that are not recognized — captured in ExtraKeysHCL. This catches typos and duplicate blocks that the HCL parser folds into unused keys. The same policy may still parse in lenient mode (PolicyParseLenient) for evaluation, but cannot be written.

Source

Thrown at acl/policy.go:672

		// Expand the short hand policy to the capabilities and
		// add to any existing capabilities
		if hv.Policy != "" {
			extraCap := expandHostVolumePolicy(hv.Policy)
			hv.Capabilities = append(hv.Capabilities, extraCap...)
		}

		// Remove the host-volume name from the extra key list.
		p.removeExtraKey(hv.Name)
	}

	// Now that we have processed all known keys, return an error if the
	// operator wrote a policy with unknown keys if we are being strict. While
	// these do not grant any extra privileges, it can be misleaing to allow
	// these and cause problems later if we add new capabilities that collide
	// with the unknown keys.
	if len(p.ExtraKeysHCL) > 0 && strict {
		return nil, fmt.Errorf("Invalid or duplicate policy keys: %v",
			strings.Join(p.ExtraKeysHCL, ", "))
	}

	p.ExtraKeysHCL = nil

	if p.Agent != nil && !isPolicyValid(p.Agent.Policy) {
		return nil, fmt.Errorf("Invalid agent policy: %#v", p.Agent)
	}

	if p.Node != nil && !isPolicyValid(p.Node.Policy) {
		return nil, fmt.Errorf("Invalid node policy: %#v", p.Node)
	}

	if p.Operator != nil {
		if p.Operator.Policy != "" && !isPolicyValid(p.Operator.Policy) {
			return nil, fmt.Errorf("Invalid operator policy: %#v", p.Operator)
		}
		for _, cap := range p.Operator.Capabilities {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. The error lists the offending keys — remove or rename each one so it matches a known block (namespace, node_pool, host_volume, agent, node, operator, sentinel, quota, plugin).
  2. Look for misspellings or plural forms of block names (e.g. 'namespaces' → 'namespace').
  3. Check for duplicated blocks of the same type in generated/merged policy files and keep a single instance.
  4. If you are only evaluating an existing policy (not writing), use acl.Parse(rules, PolicyParseLenient) instead of strict mode.

Example fix

// before
namespaces {
  name = "default"
  policy = "write"
}
// after
namespace "default" {
  policy = "write"
}
Defensive patterns

Strategy: validation

Validate before calling

knownKeys := []string{"namespace", "node_pool", "host_volume", "agent", "node", "operator", "sentinel", "quota", "plugin"}
for _, line := range strings.Split(rules, "\n") {
    if m := regexp.MustCompile(`^\s*([a-z_]+)\s*\{`).FindStringSubmatch(line); m != nil && !slices.Contains(knownKeys, m[1]) {
        return fmt.Errorf("unknown policy block %q", m[1])
    }
}

Try / catch

_, err := acl.Parse(rules, acl.PolicyParseStrict)
if err != nil {
    if strings.Contains(err.Error(), "Invalid or duplicate policy keys") {
        // the message lists the offending keys; remove/rename them in the HCL
    }
    return err
}

Prevention

When it happens

Trigger: POST/PUT of an ACL policy via the Nomad API or 'nomad acl policy apply' where the HCL/JSON has a misspelled top-level key (e.g. 'namespaces' instead of 'namespace'), an unknown key, or repeated blocks of the same type (multiple namespace/host_volume/node_pool blocks trigger leftover keys if the dedup in Parse misses them).

Common situations: Typo'd block names like 'host_volumes' or 'nodepool'; copy-pasted JSON policies with extra fields; old policies written before a schema change being re-applied under strict validation; duplicate host_volume blocks accidentally concatenated in a rendered template.

Related errors


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