hashicorp/nomad · error

stop_after cannot be a negative duration

Error message

stop_after cannot be a negative duration

What it means

errNegativeStopAfter is a disconnect-strategy validation error: stop_after must be zero or a positive duration. A negative stop_after would mean stopping the task before the disconnect happens, so DisconnectStrategy.Validate rejects the job when *ds.StopOnClientAfter < 0.

Source

Thrown at nomad/structs/group.go:28

	"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"`

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Set stop_after to a positive duration (e.g. "30m") or remove it to disable stop-on-disconnect
  2. Clamp or abs() any computed duration used in the job template
  3. Validate the job with 'nomad job validate' before submission

Example fix

// before
disconnect {
  stop_after = "-30m"
}
// after
disconnect {
  stop_after = "30m"
}
Defensive patterns

Strategy: validation

Validate before calling

if ds := grp.Disconnect; ds != nil && ds.StopOnClientAfter != nil && *ds.StopOnClientAfter < 0 {
    return fmt.Errorf("disconnect: stop_after must be >= 0")
}

Type guard

func stopAfterValid(ds *api.DisconnectStrategy) bool {
    return ds == nil || ds.StopOnClientAfter == nil || *ds.StopOnClientAfter >= 0
}

Try / catch

err := client.Jobs().Validate(job, nil, nil)
if err != nil && strings.Contains(err.Error(), "stop_after cannot be a negative duration") {
    return fmt.Errorf("fix stop_after to a positive duration: %w", err)
}

Prevention

When it happens

Trigger: Submitting a job whose group disconnect block sets stop_after to a negative duration, checked at group.go:75 in DisconnectStrategy.Validate (only when StopOnClientAfter is non-nil).

Common situations: Typos like stop_after = "-30m"; templated durations computed from data that can be negative; mixing up sign when converting between seconds/nanoseconds in generated job specs.

Related errors


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