{"record":{"id":"b61a494060a87995","repo":"hashicorp/nomad","slug":"restart-policy-q-with-d-attempts-is-ambiguous","errorCode":null,"errorMessage":"Restart policy %q with %d attempts is ambiguous","messagePattern":"Restart policy %q with (.+?) attempts is ambiguous","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/structs/structs.go","lineNumber":6575,"sourceCode":"\tif r == nil {\n\t\treturn nil\n\t}\n\tnrp := new(RestartPolicy)\n\t*nrp = *r\n\treturn nrp\n}\n\nfunc (r *RestartPolicy) Validate() error {\n\tvar mErr multierror.Error\n\tswitch r.Mode {\n\tcase RestartPolicyModeDelay, RestartPolicyModeFail:\n\tdefault:\n\t\t_ = multierror.Append(&mErr, fmt.Errorf(\"Unsupported restart mode: %q\", r.Mode))\n\t}\n\n\t// Check for ambiguous/confusing settings\n\tif r.Attempts == 0 && r.Mode != RestartPolicyModeFail {\n\t\t_ = multierror.Append(&mErr, fmt.Errorf(\"Restart policy %q with %d attempts is ambiguous\", r.Mode, r.Attempts))\n\t}\n\n\tif r.Interval.Nanoseconds() < RestartPolicyMinInterval.Nanoseconds() {\n\t\t_ = multierror.Append(&mErr, fmt.Errorf(\"Interval can not be less than %v (got %v)\", RestartPolicyMinInterval, r.Interval))\n\t}\n\tif time.Duration(r.Attempts)*r.Delay > r.Interval {\n\t\t_ = multierror.Append(&mErr,\n\t\t\tfmt.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))\n\t}\n\treturn mErr.ErrorOrNil()\n}\n\nfunc NewRestartPolicy(jobType string) *RestartPolicy {\n\tswitch jobType {\n\tcase JobTypeService, JobTypeSystem:\n\t\trp := DefaultServiceJobRestartPolicy\n\t\treturn &rp\n\tcase JobTypeBatch:","sourceCodeStart":6557,"sourceCodeEnd":6593,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/structs.go#L6557-L6593","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Set attempts to a positive integer (e.g. attempts = 2) if you want restarts.","If you truly want no restarts on failure, set mode = \"fail\" so zero attempts is valid.","Omit the restart block entirely to use the job-type default policy."],"exampleFix":"// before\nrestart {\n  interval = \"30m\"\n  attempts = 0\n  mode     = \"delay\"\n}\n// after\nrestart {\n  interval = \"30m\"\n  attempts = 2\n  mode     = \"delay\"\n}","handlingStrategy":"validation","validationCode":"if rp != nil && rp.Attempts == 0 && rp.Mode != \"fail\" {\n    return fmt.Errorf(\"restart attempts=0 requires mode=fail\")\n}","typeGuard":"func restartPolicySane(rp *structs.RestartPolicy) bool {\n    return rp.Mode == \"fail\" || rp.Attempts > 0\n}","tryCatchPattern":null,"preventionTips":["Always pair attempts > 0 with mode = \"delay\".","Use NewRestartPolicy(jobType) to get sane defaults instead of hand-building structs.","Remember attempts=0 does not mean unlimited."],"tags":["nomad","validation","job-spec","restart-policy"],"backgroundTag":"invalid-config-combination","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}