{"record":{"id":"1e40a45a3c0511f2","repo":"hashicorp/nomad","slug":"no-node-events-given-for-node-q","errorCode":null,"errorMessage":"no node events given for node %q","messagePattern":"no node events given for node %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/node_endpoint.go","lineNumber":1991,"sourceCode":"\t}\n\tn.srv.MeasureRPCRate(\"node\", structs.RateMetricWrite, args)\n\tif err != nil {\n\t\treturn structs.ErrPermissionDenied\n\t}\n\tdefer metrics.MeasureSince([]string{\"nomad\", \"client\", \"emit_events\"}, time.Now())\n\n\tif len(args.NodeEvents) == 0 {\n\t\treturn fmt.Errorf(\"no node events given\")\n\t}\n\n\tcallerNodeID := auth.AuthenticatedNodeID(args.GetIdentity())\n\tif callerNodeID == \"\" {\n\t\treturn structs.ErrPermissionDenied\n\t}\n\n\tfor nodeID, events := range args.NodeEvents {\n\t\tif len(events) == 0 {\n\t\t\treturn fmt.Errorf(\"no node events given for node %q\", nodeID)\n\t\t}\n\n\t\tnode, err := n.srv.State().NodeByID(nil, nodeID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif node == nil || !aclObj.AllowClientOp(node.NodePool) {\n\t\t\treturn structs.ErrPermissionDenied\n\t\t}\n\t\tif nodeID != callerNodeID {\n\t\t\treturn structs.ErrPermissionDenied\n\t\t}\n\t}\n\n\t_, index, err := n.srv.raftApply(structs.UpsertNodeEventsType, args)\n\tif err != nil {\n\t\tn.logger.Error(\"upserting node events failed\", \"error\", err)\n\t\treturn err","sourceCodeStart":1973,"sourceCodeEnd":2009,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/node_endpoint.go#L1973-L2009","documentation":"EmitEvents validates that every node ID key in args.NodeEvents maps to a non-empty event slice. If any individual node has zero events, the whole RPC is rejected with this per-node error. This catches partially-empty batches that pass the overall non-empty check.","triggerScenarios":"A Node.EmitEvents request whose NodeEvents map contains a key with an empty []*NodeEvent value, e.g. map[nodeID:abc]{}, from a client-side event buffer that created an entry without appending events.","commonSituations":"Custom client tooling or patched agents building the events map incrementally — inserting a placeholder key before events are appended; concurrency bugs where events are drained from a slice after the key is created.","solutions":["Only add a node's entry to the NodeEvents map when at least one event exists","Sanitize the map before the RPC: delete keys with empty slices","Review the emitting code for race conditions that empty a slice after key creation"],"exampleFix":"// before\nnodeEvents[nodeID] = pendingEvents[nodeID] // may be empty\n// after\nif len(pendingEvents[nodeID]) > 0 {\n    nodeEvents[nodeID] = pendingEvents[nodeID]\n}","handlingStrategy":"validation","validationCode":"for id, evs := range args.NodeEvents {\n    if len(evs) == 0 {\n        delete(args.NodeEvents, id)\n    }\n}\nif len(args.NodeEvents) == 0 { return nil }","typeGuard":"func allEntriesNonEmpty(m map[string][]*structs.NodeEvent) bool {\n    for _, v := range m {\n        if len(v) == 0 { return false }\n    }\n    return len(m) > 0\n}","tryCatchPattern":"err := client.Nodes().EmitEvents(args)\nif err != nil && strings.Contains(err.Error(), \"no node events given for node\") {\n    // sanitize the map and rebuild the batch\n}","preventionTips":["Insert map keys only together with at least one event","Guard against concurrent draining of event slices","Validate payload shape before RPC","Add tests for partially-empty event maps"],"tags":["nomad","rpc","events","validation"],"backgroundTag":"empty-payload-rejected","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"}