{"record":{"id":"ccda085b43489a51","repo":"hashicorp/nomad","slug":"can-t-cancel-terminal-deployment","errorCode":null,"errorMessage":"can't cancel terminal deployment","messagePattern":"can't cancel terminal deployment","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/structs/errors.go","lineNumber":78,"sourceCode":"\tErrJobRegistrationDisabled    = errors.New(errJobRegistrationDisabled)\n\tErrNoNodeConn                 = errors.New(errNoNodeConn)\n\tErrUnknownMethod              = errors.New(errUnknownMethod)\n\tErrUnknownNomadVersion        = errors.New(errUnknownNomadVersion)\n\tErrNodeLacksRpc               = errors.New(errNodeLacksRpc)\n\tErrMissingAllocID             = errors.New(errMissingAllocID)\n\tErrIncompatibleFiltering      = errors.New(errIncompatibleFiltering)\n\tErrMalformedChooseParameter   = errors.New(errMalformedChooseParameter)\n\n\t// ErrResultPaginatorCreation is returned by list RPC handlers when the\n\t// result paginator cannot be built, for example when the server cannot\n\t// evaluate a requested filter expression. api.ResultPaginatorErrorContent\n\t// duplicates its message so the CLI can match it without importing structs.\n\t// Keep the two in sync.\n\tErrResultPaginatorCreation = errors.New(errResultPaginatorCreation)\n\n\tErrUnknownNode = errors.New(ErrUnknownNodePrefix)\n\n\tErrDeploymentTerminalNoCancel    = errors.New(errDeploymentTerminalNoCancel)\n\tErrDeploymentTerminalNoFail      = errors.New(errDeploymentTerminalNoFail)\n\tErrDeploymentTerminalNoPause     = errors.New(errDeploymentTerminalNoPause)\n\tErrDeploymentTerminalNoPromote   = errors.New(errDeploymentTerminalNoPromote)\n\tErrDeploymentTerminalNoResume    = errors.New(errDeploymentTerminalNoResume)\n\tErrDeploymentTerminalNoUnblock   = errors.New(errDeploymentTerminalNoUnblock)\n\tErrDeploymentTerminalNoRun       = errors.New(errDeploymentTerminalNoRun)\n\tErrDeploymentTerminalNoSetHealth = errors.New(errDeploymentTerminalNoSetHealth)\n\tErrDeploymentRunningNoUnblock    = errors.New(errDeploymentRunningNoUnblock)\n\n\tErrCSIClientRPCIgnorable  = errors.New(\"CSI client error (ignorable)\")\n\tErrCSIClientRPCRetryable  = errors.New(\"CSI client error (retryable)\")\n\tErrCSIVolumeMaxClaims     = errors.New(\"volume max claims reached\")\n\tErrCSIVolumeUnschedulable = errors.New(\"volume is currently unschedulable\")\n\tErrCSIPluginInUse         = errors.New(\"plugin in use\")\n)\n\n// IsErrNoLeader returns whether the error is due to there being no leader.\nfunc IsErrNoLeader(err error) bool {","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/errors.go#L60-L96","documentation":"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.","triggerScenarios":"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.","commonSituations":"CI pipelines that cancel deployments race with job completion; operators re-run cleanup scripts against old deployment IDs.","solutions":["Check the deployment status ('nomad deployment status <id>') before cancelling; skip if not pending/running.","Handle the error as a no-op success in idempotent automation.","Target only active deployments, e.g. via 'nomad status <job>' to find the current deployment."],"exampleFix":"// before\ndeployments, _ := c.Jobs().Deployments(jobID, nil)\nfor _, d := range deployments { c.Deployments().Cancel(d.ID, nil) }\n// after\nfor _, d := range deployments {\n    if d.Status == \"running\" || d.Status == \"pending\" { c.Deployments().Cancel(d.ID, nil) }\n}","handlingStrategy":"type-guard","validationCode":"d, err := client.Deployments().Info(deployID, nil)\nif err != nil { return err }\nif d.Status != \"running\" && d.Status != \"pending\" {\n    return nil // already terminal, nothing to cancel\n}","typeGuard":"func deploymentActive(d *api.Deployment) bool {\n    return d.Status == \"running\" || d.Status == \"pending\"\n}","tryCatchPattern":"err := client.Deployments().Cancel(deployID, nil)\nif err != nil && strings.Contains(err.Error(), \"terminal deployment\") {\n    return nil // idempotent: already terminal\n}","preventionTips":["Check deployment status before mutation calls.","Make deployment automation idempotent by treating terminal-state errors as success.","Fetch current deployment IDs from the job rather than caching."],"tags":["deployment","state","rpc","nomad"],"backgroundTag":"invalid-state-transition","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"}