hashicorp/nomad · error

can't cancel terminal deployment

Error message

can't cancel terminal deployment

What it means

Sentinel ErrDeploymentTerminalNoCancel: a deployment API call (pause/fail/promote/etc., here cancel path) targeted a deployment whose state is already terminal (successful, cancelled, failed, or paused-paused), and terminal deployments can no longer be mutated.

Source

Thrown at nomad/structs/errors.go:78

	ErrJobRegistrationDisabled    = errors.New(errJobRegistrationDisabled)
	ErrNoNodeConn                 = errors.New(errNoNodeConn)
	ErrUnknownMethod              = errors.New(errUnknownMethod)
	ErrUnknownNomadVersion        = errors.New(errUnknownNomadVersion)
	ErrNodeLacksRpc               = errors.New(errNodeLacksRpc)
	ErrMissingAllocID             = errors.New(errMissingAllocID)
	ErrIncompatibleFiltering      = errors.New(errIncompatibleFiltering)
	ErrMalformedChooseParameter   = errors.New(errMalformedChooseParameter)

	// ErrResultPaginatorCreation is returned by list RPC handlers when the
	// result paginator cannot be built, for example when the server cannot
	// evaluate a requested filter expression. api.ResultPaginatorErrorContent
	// duplicates its message so the CLI can match it without importing structs.
	// Keep the two in sync.
	ErrResultPaginatorCreation = errors.New(errResultPaginatorCreation)

	ErrUnknownNode = errors.New(ErrUnknownNodePrefix)

	ErrDeploymentTerminalNoCancel    = errors.New(errDeploymentTerminalNoCancel)
	ErrDeploymentTerminalNoFail      = errors.New(errDeploymentTerminalNoFail)
	ErrDeploymentTerminalNoPause     = errors.New(errDeploymentTerminalNoPause)
	ErrDeploymentTerminalNoPromote   = errors.New(errDeploymentTerminalNoPromote)
	ErrDeploymentTerminalNoResume    = errors.New(errDeploymentTerminalNoResume)
	ErrDeploymentTerminalNoUnblock   = errors.New(errDeploymentTerminalNoUnblock)
	ErrDeploymentTerminalNoRun       = errors.New(errDeploymentTerminalNoRun)
	ErrDeploymentTerminalNoSetHealth = errors.New(errDeploymentTerminalNoSetHealth)
	ErrDeploymentRunningNoUnblock    = errors.New(errDeploymentRunningNoUnblock)

	ErrCSIClientRPCIgnorable  = errors.New("CSI client error (ignorable)")
	ErrCSIClientRPCRetryable  = errors.New("CSI client error (retryable)")
	ErrCSIVolumeMaxClaims     = errors.New("volume max claims reached")
	ErrCSIVolumeUnschedulable = errors.New("volume is currently unschedulable")
	ErrCSIPluginInUse         = errors.New("plugin in use")
)

// IsErrNoLeader returns whether the error is due to there being no leader.
func IsErrNoLeader(err error) bool {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Check the deployment status ('nomad deployment status <id>') before cancelling; skip if not pending/running.
  2. Handle the error as a no-op success in idempotent automation.
  3. Target only active deployments, e.g. via 'nomad status <job>' to find the current deployment.

Example fix

// before
deployments, _ := c.Jobs().Deployments(jobID, nil)
for _, d := range deployments { c.Deployments().Cancel(d.ID, nil) }
// after
for _, d := range deployments {
    if d.Status == "running" || d.Status == "pending" { c.Deployments().Cancel(d.ID, nil) }
}
Defensive patterns

Strategy: type-guard

Validate before calling

d, err := client.Deployments().Info(deployID, nil)
if err != nil { return err }
if d.Status != "running" && d.Status != "pending" {
    return nil // already terminal, nothing to cancel
}

Type guard

func deploymentActive(d *api.Deployment) bool {
    return d.Status == "running" || d.Status == "pending"
}

Try / catch

err := client.Deployments().Cancel(deployID, nil)
if err != nil && strings.Contains(err.Error(), "terminal deployment") {
    return nil // idempotent: already terminal
}

Prevention

When it happens

Trigger: Calling Deployment.Cancel (or 'nomad deployment cancel') on a deployment whose status is not active, e.g. re-running a cancel command twice or scripting cancellation after the deploy already completed.

Common situations: CI pipelines that cancel deployments race with job completion; operators re-run cleanup scripts against old deployment IDs.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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