{"record":{"id":"af5e57b0fa60e413","repo":"hashicorp/nomad","slug":"must-update-at-least-one-allocation","errorCode":null,"errorMessage":"must update at least one allocation","messagePattern":"must update at least one allocation","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/alloc_endpoint.go","lineNumber":368,"sourceCode":"\t\treturn err\n\t}\n\ta.srv.MeasureRPCRate(\"alloc\", structs.RateMetricWrite, args)\n\tif authErr != nil {\n\t\treturn structs.ErrPermissionDenied\n\t}\n\n\tdefer metrics.MeasureSince([]string{\"nomad\", \"alloc\", \"update_desired_transition\"}, time.Now())\n\n\t// Check that it is a management token.\n\tif aclObj, err := a.srv.ResolveACL(args); err != nil {\n\t\treturn err\n\t} else if !aclObj.IsManagement() {\n\t\treturn structs.ErrPermissionDenied\n\t}\n\n\t// Ensure at least a single alloc\n\tif len(args.Allocs) == 0 {\n\t\treturn fmt.Errorf(\"must update at least one allocation\")\n\t}\n\n\t// Commit this update via Raft\n\t_, index, err := a.srv.raftApply(structs.AllocUpdateDesiredTransitionRequestType, args)\n\tif err != nil {\n\t\ta.logger.Error(\"AllocUpdateDesiredTransitionRequest failed\", \"error\", err)\n\t\treturn err\n\t}\n\n\t// Setup the response\n\treply.Index = index\n\treturn nil\n}\n\n// GetServiceRegistrations returns a list of service registrations which belong\n// to the passed allocation ID.\nfunc (a *Alloc) GetServiceRegistrations(\n\targs *structs.AllocServiceRegistrationsRequest,","sourceCodeStart":350,"sourceCodeEnd":386,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/alloc_endpoint.go#L350-L386","documentation":"UpdateDesiredTransition requires the request to carry at least one allocation ID in args.Allocs; it is a management-only RPC applied via Raft. If the request has an empty Allocs map, the server rejects it with 'must update at least one allocation' instead of committing a no-op Raft entry.","triggerScenarios":"Calling the Alloc.UpdateDesiredTransition RPC (internal, management ACL) with an empty Allocs map — e.g. a node drain/reschedule reconciliation pass computing no allocs to transition but still issuing the RPC.","commonSituations":"Custom tooling or plugins driving node drains; garbage-collection code paths that compute an empty transition set; scripted management calls against the internal API.","solutions":["Guard the call site: skip the RPC entirely when len(args.Allocs) == 0","Populate args.Allocs with at least one allocation ID and its desired transition before calling","Check the ACL token — only management tokens may call this endpoint at all"],"exampleFix":"// before\nsrv.raftApply(structs.AllocUpdateDesiredTransitionRequestType, args)\n// after\nif len(args.Allocs) > 0 {\n    srv.raftApply(structs.AllocUpdateDesiredTransitionRequestType, args)\n}","handlingStrategy":"validation","validationCode":"if len(args.Allocs) == 0 {\n    return nil // skip UpdateDesiredTransition entirely\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"must update at least one allocation\") {\n    // treat as no-op: nothing to transition\n}","preventionTips":["Always guard Raft-apply calls with empty-payload checks","Build the Allocs map before constructing the request, not after","Unit-test reconciliation paths that can produce empty transition sets"],"tags":["nomad","rpc","validation","allocations"],"backgroundTag":"empty-request-payload","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"}