hashicorp/nomad · warning
Reschedule policy has unlimited attempts enabled and a low d
Error message
Reschedule policy has unlimited attempts enabled and a low delay; reschedule thrashing possible
What it means
TaskGroup.Validate warns when the group's reschedule policy enables Unlimited attempts while Delay is under 5 seconds. Unlimited fast retries can cause reschedule thrashing, repeatedly restarting the allocation in a tight loop. It is a warning-level validation error appended to the group multierror.
Source
Thrown at nomad/structs/structs.go:7673
if tg.StopAfterClientDisconnect != nil {
mErr.Errors = append(mErr.Errors, errors.New("StopAfterClientDisconnect is deprecated and ignored favor of Disconnect.StopOnClientAfter"))
}
if tg.PreventRescheduleOnLost {
mErr.Errors = append(mErr.Errors, errors.New("PreventRescheduleOnLost is deprecated and ignored in favor of Disconnect.Replace"))
}
// Warn about unbounded rescheduling which may cause thrashing if tasks have
// unlimited attempts and a low delay.
//
// The 5 second gate on the delay is used as this was the previous minimum
// value which did not produce a warning. It therefore feels like the right
// gate to use.
if rp := tg.ReschedulePolicy; rp != nil && rp.Unlimited && rp.Delay < 5*time.Second {
mErr.Errors = append(
mErr.Errors,
errors.New("Reschedule policy has unlimited attempts enabled and a low delay; reschedule thrashing possible"),
)
}
// Check for mbits network field
if len(tg.Networks) > 0 && tg.Networks[0].MBits > 0 {
mErr.Errors = append(mErr.Errors, fmt.Errorf("mbits has been deprecated as of Nomad 0.12.0. Please remove mbits from the network block"))
}
// Validate group-level services.
for _, s := range tg.Services {
if err := s.Warnings(); err != nil {
err = multierror.Prefix(err, fmt.Sprintf("Service %q:", s.Name))
mErr = *multierror.Append(&mErr, err)
}
}
for _, t := range tg.Tasks {
if err := t.Warnings(); err != nil {View on GitHub (pinned to 482b49bf1a)
Solutions
- Set reschedule delay >= 5s, e.g. delay = "30s".
- Or disable unlimited: `reschedule { unlimited = false, attempts = 5 }`.
- Or remove the reschedule block entirely to use sensible defaults.
Example fix
// before
reschedule {
unlimited = true
delay = "1s"
}
// after
reschedule {
unlimited = false
attempts = 5
delay = "30s"
max_delay = "1h"
} Defensive patterns
Strategy: validation
Validate before calling
if rp := tg.ReschedulePolicy; rp != nil && rp.Unlimited && rp.Delay < 5*time.Second {
log.Warn("unlimited reschedule with low delay may thrash")
} Type guard
func isThrashRisk(rp *structs.ReschedulePolicy) bool {
return rp != nil && rp.Unlimited && rp.Delay < 5*time.Second
} Prevention
- Keep reschedule delay >= 5s when unlimited is enabled.
- Use attempts + interval instead of unlimited for non-critical services.
- Check namespace/agent reschedule defaults too.
- Monitor restart counts in the Nomad UI for thrash symptoms.
When it happens
Trigger: A group has `reschedule { unlimited = true, delay = "1s" }` (Delay < 5s and DelayFunction leading to low delays) — either set explicitly or inherited from a namespace/agent default when the job overrides the policy.
Common situations: Hand-written reschedule blocks with very small delays; copied examples tuned for batch jobs but applied to services that will never become healthy.
Related errors
- Interval cannot be less than %v (got %v)
- Nomad can only make %v attempts in %v with initial delay %v
- Nomad can only make %v attempts in %v with initial delay %v,
- Lock delay and TTL must be positive
- https_handshake_timeout must be >= 0
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/7e08e4e82317c03d.
Report an issue: GitHub.