hashicorp/nomad · error

evals field must not be set

Error message

evals field must not be set

What it means

Nomad's UpdateAlloc RPC rejects client-submitted evaluations. Evals are generated server-side inside the endpoint before the Raft update, so a request carrying args.Evals is treated as invalid/malicious input and rejected. This protects the internal scheduler workflow from clients fabricating evaluations.

Source

Thrown at nomad/node_endpoint.go:1556

		return fmt.Errorf("failed to retrieve node %s: %v", targetNodeID, err)
	}
	if node == nil {
		return fmt.Errorf("node %s not found", targetNodeID)
	}
	if !aclObj.AllowClientOp(node.NodePool) {
		return structs.ErrPermissionDenied
	}
	if err := auth.AuthorizeSameNode(args.GetIdentity(), targetNodeID); err != nil {
		return err
	}
	if node.UnresponsiveStatus() {
		return fmt.Errorf("node %s is not allowed to update allocs while in status %s", targetNodeID, node.Status)
	}

	// Ensure that evals aren't set from client RPCs
	// We create them here before the raft update
	if len(args.Evals) != 0 {
		return fmt.Errorf("evals field must not be set")
	}

	// Update modified timestamp for client initiated allocation updates
	now := time.Now()
	var evals []*structs.Evaluation

	for _, allocToUpdate := range args.Alloc {
		evalTriggerBy := ""
		allocToUpdate.ModifyTime = now.UTC().UnixNano()

		alloc, _ := n.srv.State().AllocByID(nil, allocToUpdate.ID)
		if alloc == nil {
			continue
		}

		if !allocToUpdate.TerminalStatus() && alloc.ClientStatus != structs.AllocClientStatusUnknown {
			continue
		}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Clear the Evals field on the NodeUpdateAllocRequest before sending it (args.Evals = nil)
  2. Only populate the allowed fields (Allocation fields like TaskStates/DesiredStatus) and construct the request with a fresh structs.NodeUpdateAllocRequest
  3. If you need to create an eval as a user, use the Eval endpoints or the Nomad API /v1/evaluations, not UpdateAlloc
  4. Check your client code for copying of a structs.Allocation into args and strip internal-only fields

Example fix

// before
args := structs.NodeUpdateAllocRequest{Eval: myEval, Evals: evals, Alloc: alloc}
// after
args := structs.NodeUpdateAllocRequest{Alloc: alloc} // evals are created by the server
Defensive patterns

Strategy: validation

Validate before calling

if len(req.Evals) != 0 {
    return fmt.Errorf("client must not set Evals on NodeUpdateAllocRequest")
}
// or proactively: req.Evals = nil before sending

Type guard

func evalsUnset(req *structs.NodeUpdateAllocRequest) bool { return len(req.Evals) == 0 }

Prevention

When it happens

Trigger: A Nomad client (node agent) sends a Node.UpdateAlloc RPC with the Evals field populated; only CreateEval, MigrateToken, Preempted fields are accepted from clients.

Common situations: Custom or patched Nomad clients (e.g. third-party schedulers, forked drivers, or integration tooling) that copy a full Allocation struct into the update request instead of zeroing Evals; version drift where a newer client sets Evals.

Related errors


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