hashicorp/nomad · error

Restart policy %q with %d attempts is ambiguous

Error message

Restart policy %q with %d attempts is ambiguous

What it means

RestartPolicy.Validate() flags a policy where attempts is 0 but mode is not "fail". With mode "delay" and zero attempts Nomad would never restart within the window, making the interval/attempts block meaningless and ambiguous. Nomad refuses to guess the intended behavior.

Source

Thrown at nomad/structs/structs.go:6575

	if r == nil {
		return nil
	}
	nrp := new(RestartPolicy)
	*nrp = *r
	return nrp
}

func (r *RestartPolicy) Validate() error {
	var mErr multierror.Error
	switch r.Mode {
	case RestartPolicyModeDelay, RestartPolicyModeFail:
	default:
		_ = multierror.Append(&mErr, fmt.Errorf("Unsupported restart mode: %q", r.Mode))
	}

	// Check for ambiguous/confusing settings
	if r.Attempts == 0 && r.Mode != RestartPolicyModeFail {
		_ = multierror.Append(&mErr, fmt.Errorf("Restart policy %q with %d attempts is ambiguous", r.Mode, r.Attempts))
	}

	if r.Interval.Nanoseconds() < RestartPolicyMinInterval.Nanoseconds() {
		_ = multierror.Append(&mErr, fmt.Errorf("Interval can not be less than %v (got %v)", RestartPolicyMinInterval, r.Interval))
	}
	if time.Duration(r.Attempts)*r.Delay > r.Interval {
		_ = multierror.Append(&mErr,
			fmt.Errorf("Nomad can't restart the TaskGroup %v times in an interval of %v with a delay of %v", r.Attempts, r.Interval, r.Delay))
	}
	return mErr.ErrorOrNil()
}

func NewRestartPolicy(jobType string) *RestartPolicy {
	switch jobType {
	case JobTypeService, JobTypeSystem:
		rp := DefaultServiceJobRestartPolicy
		return &rp
	case JobTypeBatch:

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Set attempts to a positive integer (e.g. attempts = 2) if you want restarts.
  2. If you truly want no restarts on failure, set mode = "fail" so zero attempts is valid.
  3. Omit the restart block entirely to use the job-type default policy.

Example fix

// before
restart {
  interval = "30m"
  attempts = 0
  mode     = "delay"
}
// after
restart {
  interval = "30m"
  attempts = 2
  mode     = "delay"
}
Defensive patterns

Strategy: validation

Validate before calling

if rp != nil && rp.Attempts == 0 && rp.Mode != "fail" {
    return fmt.Errorf("restart attempts=0 requires mode=fail")
}

Type guard

func restartPolicySane(rp *structs.RestartPolicy) bool {
    return rp.Mode == "fail" || rp.Attempts > 0
}

Prevention

When it happens

Trigger: A task group restart block with attempts = 0 (explicitly or via zero-value struct) while mode is "delay"; also common when interval/attempts are set programmatically and attempts defaults to 0.

Common situations: Hand-written job files that set interval/delay but forget attempts; templating code that renders an empty attempts field; users expecting attempts=0 to mean 'unlimited' (it does not).

Related errors


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