hashicorp/nomad · error

task.consul.partition %q must match group.consul.partition %

Error message

task.consul.partition %q must match group.consul.partition %q if both are set

What it means

Nomad supports Consul admin partitions (Consul Enterprise). When a job sets a partition at both the task-level `consul` block and the group-level `consul` block, they must be identical; otherwise the job fails validation. validateTaskPartitionMatchesGroup enforces this consistency to avoid ambiguous cluster/partition routing.

Source

Thrown at nomad/job_endpoint_hook_consul.go:29

// jobConsulHook is a job registration admission controller for Consul
// configuration in Consul, Service, and Template blocks
type jobConsulHook struct {
	srv *Server
}

func (jobConsulHook) Name() string {
	return "consul"
}

// validateTaskPartitionMatchesGroup validates that any partition set for the
// task.Consul matches any partition set for the group
func (jobConsulHook) validateTaskPartitionMatchesGroup(groupPartition string, taskConsul *structs.Consul) error {
	if taskConsul.Partition == "" || groupPartition == "" {
		return nil
	}
	if taskConsul.Partition != groupPartition {
		return fmt.Errorf("task.consul.partition %q must match group.consul.partition %q if both are set", taskConsul.Partition, groupPartition)
	}
	return nil
}

// mutateImpl ensures that the job's Consul blocks have been configured with the
// correct Consul cluster if unset, and sets constraints on the Consul admin
// partition if set. This should be called by the Mutate method.
func (jobConsulHook) mutateImpl(job *structs.Job, defaultCluster string) *structs.Job {
	for _, group := range job.TaskGroups {
		if group.Consul != nil {
			if group.Consul.Cluster == "" {
				group.Consul.Cluster = defaultCluster
			}
			if group.Consul.Partition != "" {
				group.Constraints = append(group.Constraints,
					newConsulPartitionConstraint(group.Consul.Cluster, group.Consul.Partition))
			}
		}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Set the task's `consul.partition` to the same value as the group's `consul.partition`.
  2. Remove the task-level `partition` field and rely on the group-level setting (or vice versa).
  3. Review which Consul partition the workload should join and align both blocks to it.

Example fix

// before
group "api" {
  consul { partition = "prod" }
  task "web" {
    consul { partition = "staging" }
  }
}
// after
group "api" {
  consul { partition = "prod" }
  task "web" {
    consul { partition = "prod" }
  }
}
Defensive patterns

Strategy: validation

Validate before calling

function validatePartitionMatch(task, group) {
  const tp = task.consul?.partition, gp = group.consul?.partition;
  if (tp && gp && tp !== gp) {
    throw new Error(`task.consul.partition "${tp}" must match group.consul.partition "${gp}"`);
  }
}

Type guard

function partitionsCompatible(taskConsul, groupConsul) { const tp = taskConsul?.partition || '', gp = groupConsul?.partition || ''; return !tp || !gp || tp === gp; }

Prevention

When it happens

Trigger: Submitting a job where `task { consul { partition = "foo" } }` differs from the enclosing `group { consul { partition = "bar" } }`; both must be non-empty and equal, otherwise the hook errors.

Common situations: Migrating from group-level to task-level consul configuration (or vice versa) and updating only one partition; copy-paste between jobs targeting different Consul partitions; teams with per-partition staging/production layouts misconfiguring one level.

Related errors


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