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

  1. Change the job type to 'service' or 'batch' (e.g. type = "service") if stop_after semantics fit the workload
  2. Remove the disconnect.stop_after field from the job specification
  3. Use lost_after instead of stop_after if the job type cannot be changed and lost behavior is acceptable
  4. 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

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


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