hashicorp/nomad · error

Task Group %v should have a restart policy

Error message

Task Group %v should have a restart policy

What it means

Nomad task group validation (nomad/structs/structs.go) requires every task group to have a restart policy. If tg.RestartPolicy is nil after job submission (the API normally interpolates a default), the validator appends "Task Group %v should have a restart policy" naming the group, since restart behavior is mandatory for scheduling semantics.

Source

Thrown at nomad/structs/structs.go:7184

	if j.Type == JobTypeSystem {
		if tg.Affinities != nil {
			mErr = multierror.Append(mErr, fmt.Errorf("System jobs may not have an affinity block"))
		}
	} else {
		for idx, affinity := range tg.Affinities {
			if err := affinity.Validate(); err != nil {
				outer := fmt.Errorf("Affinity %d validation failed: %s", idx+1, err)
				mErr = multierror.Append(mErr, outer)
			}
		}
	}

	if tg.RestartPolicy != nil {
		if err := tg.RestartPolicy.Validate(); err != nil {
			mErr = multierror.Append(mErr, err)
		}
	} else {
		mErr = multierror.Append(mErr, fmt.Errorf("Task Group %v should have a restart policy", tg.Name))
	}

	if j.Type == JobTypeSystem {
		if tg.Spreads != nil {
			mErr = multierror.Append(mErr, fmt.Errorf("System jobs may not have a spread block"))
		}
	} else {
		for idx, spread := range tg.Spreads {
			if err := spread.Validate(); err != nil {
				outer := fmt.Errorf("Spread %d validation failed: %s", idx+1, err)
				mErr = multierror.Append(mErr, outer)
			}
		}
	}

	if j.Type == JobTypeSystem || j.Type == JobTypeSysBatch {
		if tg.ReschedulePolicy != nil {
			mErr = multierror.Append(mErr, fmt.Errorf("System or sysbatch jobs should not have a reschedule policy"))

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Set a restart policy on the task group (or submit through the HTTP API / `nomad job run`, which canonicalizes and fills defaults).
  2. Call Job.Canonicalize() (or the API job's Canonicalize) before validation to materialize default restart policies.
  3. If hand-writing JSON, include a `RestartPolicy` object with Interval, Attempts, Delay, and Mode fields.

Example fix

// before
group "web" {
  count = 2
  // no restart policy
}
// after
group "web" {
  count = 2
  restart {
    attempts = 2
    interval = "30m"
    delay    = "15s"
    mode     = "fail"
  }
}
Defensive patterns

Strategy: validation

Validate before calling

func ensureRestartPolicy(tg map[string]interface{}) error {
  if _, ok := tg["restart"]; !ok {
    tg["restart"] = map[string]interface{}{
      "attempts": 2, "interval": "30m", "delay": "15s", "mode": "fail",
    }
  }
  return nil
}

Type guard

func hasRestartPolicy(tg *api.TaskGroup) bool { return tg != nil && tg.RestartPolicy != nil }

Try / catch

if err := job.Validate(); err != nil {
  if strings.Contains(err.Error(), "should have a restart policy") {
    return fmt.Errorf("call Canonicalize() or add a restart block: %w", err)
  }
  return err
}

Prevention

When it happens

Trigger: Constructing a structs.TaskGroup programmatically (Go API or raw JSON job submission) without setting RestartPolicy, bypassing the API layer that normally fills in defaults; submitting a group with restart {} stripped out and no policy materialized.

Common situations: Building jobs via the Nomad Go SDK/structs package directly; crafting minimal JSON job payloads by hand for `nomad job run` API calls; version drift where a tool deletes restart blocks assuming server-side defaulting that only happens through the HTTP API canonicalization.

Related errors


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