{"record":{"id":"db93409d3f7995b4","repo":"weaviate/weaviate","slug":"unexpected-added-node-q-is-also-in-remaining-for","errorCode":null,"errorMessage":"unexpected added node %q is also in remaining for shard %q","messagePattern":"unexpected added node %q is also in remaining for shard %q","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cluster/replication/manager.go","lineNumber":569,"sourceCode":"\t\tscalePlan.ShardReplicationScaleActions[shardName] = cmd.ShardReplicationScaleActions{\n\t\t\tRemoveNodes: make(map[string]struct{}, len(nodes.Removed)),\n\t\t\tAddNodes:    make(map[string]string, len(nodes.Added)),\n\t\t}\n\n\t\tfor node := range nodes.Removed {\n\t\t\tif _, ok := nodes.Remaining[node]; ok {\n\t\t\t\treturn nil, fmt.Errorf(\"unexpected removed node %q is also in remaining for shard %q\", node, shardName)\n\t\t\t}\n\t\t\tif _, ok := nodes.Added[node]; ok {\n\t\t\t\treturn nil, fmt.Errorf(\"unexpected removed node %q is also in added for shard %q\", node, shardName)\n\t\t\t}\n\n\t\t\tscalePlan.ShardReplicationScaleActions[shardName].RemoveNodes[node] = struct{}{}\n\t\t}\n\n\t\tfor node := range nodes.Added {\n\t\t\tif _, ok := nodes.Remaining[node]; ok {\n\t\t\t\treturn nil, fmt.Errorf(\"unexpected added node %q is also in remaining for shard %q\", node, shardName)\n\t\t\t}\n\t\t\tif _, ok := nodes.Removed[node]; ok {\n\t\t\t\treturn nil, fmt.Errorf(\"unexpected added node %q is also in removed for shard %q\", node, shardName)\n\t\t\t}\n\n\t\t\t// pick a random source node from the remaining nodes\n\t\t\tvar sourceNode string\n\t\t\ti := rand.Intn(len(nodes.Remaining))\n\t\t\tj := 0\n\t\t\tfor n := range nodes.Remaining {\n\t\t\t\tif j == i {\n\t\t\t\t\tsourceNode = n\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tj++\n\t\t\t}\n\n\t\t\tscalePlan.ShardReplicationScaleActions[shardName].AddNodes[node] = sourceNode","sourceCodeStart":551,"sourceCodeEnd":587,"githubUrl":"https://github.com/weaviate/weaviate/blob/75aa4b6d11f8818305aafd4440b4e32794f7ca04/cluster/replication/manager.go#L551-L587","documentation":"A node marked as added to a shard also appears in the remaining set. A node cannot be both newly added and already present, so this contradictory diff aborts plan generation to prevent no-op or conflicting replication actions.","triggerScenarios":"Calling QueryReplicationScalePlan where the desired assignment includes an existing shard node in the Added set (not just Remaining), typically because the diff classified already-present nodes as new.","commonSituations":"Diff tooling comparing full node lists instead of set differences (not subtracting current replicas); applying the same scale plan twice with unrefreshed state.","solutions":["Subtract the current replica set from the desired set before classifying nodes as added","Classify nodes present in both before and after as Remaining, not Added","Validate Added ∩ Remaining = ∅ before submitting the request","Re-query the scale plan so the server computes the diff itself"],"exampleFix":"// before\nremaining: {node1}, added: {node1, node2} // node1 double-counted\n// after\nremaining: {node1}, added: {node2}","handlingStrategy":"validation","validationCode":"added := setDifference(desired, current) // only truly new nodes\nremaining := intersect(desired, current)\n// assert added ∩ remaining == ∅","typeGuard":null,"tryCatchPattern":"plan, err := mgr.QueryReplicationScalePlan(req)\nif err != nil && strings.Contains(err.Error(), \"added node\") && strings.Contains(err.Error(), \"is also in remaining\") {\n    // recompute added set as desired minus current\n}","preventionTips":["Classify nodes present before and after as Remaining, never Added","Use set operations (difference/intersection), not raw list appends","Refresh current shard state before each scale-plan request (no stale caches)"],"tags":["replication","scale-plan","invariant"],"backgroundTag":"invalid-node-diff","analyzedSha":"75aa4b6d11f8818305aafd4440b4e32794f7ca04","analyzedAt":"2026-09-04T14:58:20.392Z","contentChangedAt":"2026-09-04T14:58:20.392Z","schemaVersion":2},"datasetVersion":"2026-09-11T21:17:09.523Z"}