{"record":{"id":"d856d4b4d43673f3","repo":"hashicorp/nomad","slug":"eval-s-is-not-safe-to-delete","errorCode":null,"errorMessage":"eval %s is not safe to delete","messagePattern":"eval (.+?) is not safe to delete","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/eval_endpoint.go","lineNumber":526,"sourceCode":"\t// Iterate the evaluations and ensure they are safe to delete. It is\n\t// possible passed evals are not safe to delete and would make Nomads state\n\t// a little wonky. The nature of the RPC return error, means a single\n\t// unsafe eval ID fails the whole call.\n\tfor _, evalID := range args.EvalIDs {\n\n\t\tevalInfo, err := serverStateSnapshot.EvalByID(ws, evalID)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to lookup eval: %v\", err)\n\t\t}\n\t\tif evalInfo == nil {\n\t\t\treturn errors.New(\"eval not found\")\n\t\t}\n\t\tok, err := serverStateSnapshot.EvalIsUserDeleteSafe(ws, evalInfo)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"eval %s is not safe to delete\", evalInfo.ID)\n\t\t}\n\t\tcount++\n\t}\n\n\t// Generate the Raft request object using the reap request object. This\n\t// avoids adding new Raft messages types and follows the existing reap\n\t// flow.\n\traftReq := structs.EvalReapRequest{\n\t\tEvals:         args.EvalIDs,\n\t\tUserInitiated: true,\n\t\tWriteRequest:  args.WriteRequest,\n\t}\n\n\t// Update via Raft.\n\t_, index, err := e.srv.raftApply(structs.EvalDeleteRequestType, &raftReq)\n\tif err != nil {\n\t\treturn err\n\t}","sourceCodeStart":508,"sourceCodeEnd":544,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/eval_endpoint.go#L508-L544","documentation":"Returned by Evaluations.Delete when EvalIsUserDeleteSafe determines that one of the requested evals must not be user-deleted (e.g. it is tied to a live scheduling/deployment flow). The entire delete is aborted to keep eval accounting consistent.","triggerScenarios":"Calling eval delete with an eval ID whose EvalIsUserDeleteSafe check returns false — typically evals still referenced by active allocations, deployments, or newly-created evals not yet eligible for user deletion.","commonSituations":"Attempting to clean up evals while a deployment is in progress; deleting evals that the blocked-eval broker or deployment watcher still owns; scripting bulk deletes over all evals without filtering to safe ones.","solutions":["Wait for associated deployments/allocations to finish, or cancel them first, then retry","Filter target evals to terminal states (complete/cancelled) before deleting","Delete the evals via filter with the broker paused so safety checks apply consistently"],"exampleFix":"// before\nclient.Evaluations().Delete(allEvalIDs, nil, \"\")\n// after\nsafeIDs := filter(terminalEvalsOnly)\nclient.Evaluations().Delete(safeIDs, nil, \"\")","handlingStrategy":"retry","validationCode":"eval, _, _ := client.Evaluations().Get(id)\nif eval != nil && eval.Status != \"complete\" && eval.Status != \"cancelled\" {\n    return fmt.Errorf(\"eval %s not terminal; skip delete\", id)\n}","typeGuard":null,"tryCatchPattern":"err := deleteEvals(ids)\nif err != nil && strings.Contains(err.Error(), \"is not safe to delete\") {\n    time.Sleep(backoff) // wait for deployments to settle, then retry\n    return retry(ids)\n}","preventionTips":["Only delete evals in terminal states","Pause eval broker and wait for active deployments to finish before cleanup","Exclude evals tied to in-progress deployments from bulk deletes"],"tags":["nomad","rpc","scheduler","safety-check"],"backgroundTag":"resource-not-deletable","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"}