hashicorp/nomad · error
stop_after can only be used with service or batch job types
Error message
stop_after can only be used with service or batch job types
What it means
Nomad's DisconnectStrategy allows configuring stop_after to stop tasks after a client disconnect, but this behavior is only supported for service and batch jobs. When a job declares stop_after while its Type is neither 'service' nor 'batch' (e.g. 'system'), Job.Validate appends errStopAfterNonService to the multierror. The error is a sentinel declared in nomad/structs/group.go and wrapped/compared by identity in tests.
Source
Thrown at nomad/structs/group.go:29
"github.com/hashicorp/go-multierror"
)
const (
// ReconcileOption is used to specify the behavior of the reconciliation process
// between the original allocations and the replacements when a previously
// disconnected client comes back online.
ReconcileOptionKeepOriginal = "keep_original"
ReconcileOptionKeepReplacement = "keep_replacement"
ReconcileOptionBestScore = "best_score"
ReconcileOptionLongestRunning = "longest_running"
)
var (
// Disconnect strategy validation errors
errStopAndLost = errors.New("Disconnect cannot be configured with both lost_after and stop_after")
errNegativeLostAfter = errors.New("lost_after cannot be a negative duration")
errNegativeStopAfter = errors.New("stop_after cannot be a negative duration")
errStopAfterNonService = errors.New("stop_after can only be used with service or batch job types")
errInvalidReconcile = errors.New("reconcile option is invalid")
)
func NewDefaultDisconnectStrategy() *DisconnectStrategy {
return &DisconnectStrategy{
Replace: new(true),
Reconcile: ReconcileOptionBestScore,
}
}
// Disconnect strategy defines how both clients and server should behave in case of
// disconnection between them.
type DisconnectStrategy struct {
// Defines for how long the server will consider the unresponsive node as
// disconnected but alive instead of lost.
LostAfter time.Duration `mapstructure:"lost_after" hcl:"lost_after,optional"`
// Defines for how long a disconnected client will keep its allocations running.View on GitHub (pinned to 482b49bf1a)
Solutions
- Change the job type to 'service' or 'batch' (e.g. type = "service") if stop_after semantics fit the workload
- Remove the disconnect.stop_after field from the job specification
- Use lost_after instead of stop_after if the job type cannot be changed and lost behavior is acceptable
- Move the workload to a service/batch job where stop_after is supported
Example fix
# before
job "node-monitor" {
type = "system"
disconnect {
stop_after = "10m"
}
}
# after
job "node-monitor" {
type = "service"
disconnect {
stop_after = "10m"
}
} Defensive patterns
Strategy: validation
Validate before calling
func validStopAfterType(jobType string) bool {
return jobType == "service" || jobType == "batch"
}
if job.Disconnect != nil && job.Disconnect.StopAfter > 0 && !validStopAfterType(job.Type) {
return fmt.Errorf("stop_after requires service or batch type; got %q", job.Type)
} Prevention
- Keep disconnect blocks only in service/batch job templates
- Validate jobs with `nomad job validate` before submit
- Never share disconnect config blocks across system and service jobs
When it happens
Trigger: Submitting or validating a job whose block defines disconnect { stop_after = <duration> } while job.type is 'system' (or any type other than 'service' or 'batch'). Triggered inside Job.Validate via the multierror append at group.go:79.
Common situations: Template-ing a system job (e.g. a per-node monitoring task) and copying a disconnect block from a service job spec; upgrading workloads to Nomad versions that introduced disconnect strategies and applying the new fields without checking job type constraints.
Related errors
- reconcile option is invalid
- ShutdownDelay must be a positive value
- plugin not found
- wait config is nil or empty
- wait config is empty
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/d3d7a7939a6f7fa5.
Report an issue: GitHub.