{"record":{"id":"a376d5b00e9acfcc","repo":"hashicorp/nomad","slug":"node-ids-missing","errorCode":null,"errorMessage":"node ids missing","messagePattern":"node ids missing","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/state/state_store.go","lineNumber":1071,"sourceCode":"\n\treturn nil\n}\n\n// DeleteNode deregisters a batch of nodes\nfunc (s *StateStore) DeleteNode(msgType structs.MessageType, index uint64, nodes []string) error {\n\ttxn := s.db.WriteTxn(index)\n\tdefer txn.Abort()\n\n\terr := s.deleteNodeTxn(txn, index, nodes)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn txn.Commit()\n}\n\nfunc (s *StateStore) deleteNodeTxn(txn *txn, index uint64, nodes []string) error {\n\tif len(nodes) == 0 {\n\t\treturn fmt.Errorf(\"node ids missing\")\n\t}\n\n\tfor _, nodeID := range nodes {\n\t\texisting, err := txn.First(\"nodes\", \"id\", nodeID)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"node lookup failed: %s: %v\", nodeID, err)\n\t\t}\n\t\tif existing == nil {\n\t\t\treturn fmt.Errorf(\"node not found: %s\", nodeID)\n\t\t}\n\n\t\t// Delete the node\n\t\tif err := txn.Delete(\"nodes\", existing); err != nil {\n\t\t\treturn fmt.Errorf(\"node delete failed: %s: %v\", nodeID, err)\n\t\t}\n\n\t\tnode := existing.(*structs.Node)\n\t\tif err := deleteNodeCSIPlugins(txn, node, index); err != nil {","sourceCodeStart":1053,"sourceCodeEnd":1089,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/state/state_store.go#L1053-L1089","documentation":"deleteNodeTxn rejects a request to delete nodes when the supplied node ID slice is empty. It is an internal guard so a no-op batch delete never commits an index bump or emits node deregistration events. It surfaces to callers of StateStore.DeleteNode as a generic error.","triggerScenarios":"Calling StateStore.DeleteNode (indirectly via deleteNodeTxn) with a zero-length nodes slice, typically because the caller built the list from an empty request payload.","commonSituations":"An HTTP Node.Deregister (batch) request with an empty node_ids array; a derived list of node IDs filtered down to nothing before calling DeleteNode.","solutions":["Return early with a success/no-op if len(nodeIDs) == 0 before calling DeleteNode.","Reject the request in the HTTP/RPC handler with a 400/invalid-params error before touching the state store.","Inspect where the ID list is built and fix the upstream filtering that dropped all IDs."],"exampleFix":"// before\nif err := store.DeleteNode(1000, nodeIDs); err != nil { ... }\n// after\nif len(nodeIDs) == 0 {\n    return nil // or return a 400 invalid-params error\n}\nif err := store.DeleteNode(1000, nodeIDs); err != nil { ... }","handlingStrategy":"validation","validationCode":"if len(nodeIDs) == 0 {\n    return fmt.Errorf(\"at least one node id is required\")\n}","typeGuard":null,"tryCatchPattern":"if err := store.DeleteNode(idx, nodeIDs); err != nil {\n    if err.Error() == \"node ids missing\" {\n        return fmt.Errorf(\"no node ids supplied\")\n    }\n    return err\n}","preventionTips":["Validate request payloads at the RPC/HTTP layer before touching the state store.","Check that ID-list construction (filters, dedup) never silently empties the list.","Return explicit empty-parameter errors to API clients with 400 status."],"tags":["nomad","state-store","input-validation","node-deregistration"],"backgroundTag":"empty-required-parameter","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"}