hashicorp/nomad · error

eval %s is not safe to delete

Error message

eval %s is not safe to delete

What it means

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.

Source

Thrown at nomad/eval_endpoint.go:526

	// Iterate the evaluations and ensure they are safe to delete. It is
	// possible passed evals are not safe to delete and would make Nomads state
	// a little wonky. The nature of the RPC return error, means a single
	// unsafe eval ID fails the whole call.
	for _, evalID := range args.EvalIDs {

		evalInfo, err := serverStateSnapshot.EvalByID(ws, evalID)
		if err != nil {
			return fmt.Errorf("failed to lookup eval: %v", err)
		}
		if evalInfo == nil {
			return errors.New("eval not found")
		}
		ok, err := serverStateSnapshot.EvalIsUserDeleteSafe(ws, evalInfo)
		if err != nil {
			return err
		}
		if !ok {
			return fmt.Errorf("eval %s is not safe to delete", evalInfo.ID)
		}
		count++
	}

	// Generate the Raft request object using the reap request object. This
	// avoids adding new Raft messages types and follows the existing reap
	// flow.
	raftReq := structs.EvalReapRequest{
		Evals:         args.EvalIDs,
		UserInitiated: true,
		WriteRequest:  args.WriteRequest,
	}

	// Update via Raft.
	_, index, err := e.srv.raftApply(structs.EvalDeleteRequestType, &raftReq)
	if err != nil {
		return err
	}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Wait for associated deployments/allocations to finish, or cancel them first, then retry
  2. Filter target evals to terminal states (complete/cancelled) before deleting
  3. Delete the evals via filter with the broker paused so safety checks apply consistently

Example fix

// before
client.Evaluations().Delete(allEvalIDs, nil, "")
// after
safeIDs := filter(terminalEvalsOnly)
client.Evaluations().Delete(safeIDs, nil, "")
Defensive patterns

Strategy: retry

Validate before calling

eval, _, _ := client.Evaluations().Get(id)
if eval != nil && eval.Status != "complete" && eval.Status != "cancelled" {
    return fmt.Errorf("eval %s not terminal; skip delete", id)
}

Try / catch

err := deleteEvals(ids)
if err != nil && strings.Contains(err.Error(), "is not safe to delete") {
    time.Sleep(backoff) // wait for deployments to settle, then retry
    return retry(ids)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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