{"record":{"id":"59be83e785d719ce","repo":"dgraph-io/dgraph","slug":"nil-restore-request","errorCode":null,"errorMessage":"nil restore request","messagePattern":"nil restore request","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"worker/online_restore.go","lineNumber":201,"sourceCode":"\t// We should wait to ensure that we have seen all the updates until the StartTs\n\t// of this restore transaction.\n\tif err := posting.Oracle().WaitForTs(ctx, req.RestoreTs); err != nil {\n\t\treturn nil, errors.Wrapf(err, \"cannot wait for restore ts %d\", req.RestoreTs)\n\t}\n\n\tglog.Infof(\"Proposing restore request\")\n\terr := groups().Node.proposeAndWait(ctx, &pb.Proposal{Restore: req})\n\tif err != nil {\n\t\treturn &emptyRes, errors.Wrapf(err, errRestoreProposal)\n\t}\n\n\treturn &emptyRes, nil\n}\n\n// TODO(DGRAPH-1232): Ensure all groups receive the restore proposal.\nfunc handleRestoreProposal(ctx context.Context, req *pb.RestoreRequest, pidx uint64) error {\n\tif req == nil {\n\t\treturn errors.Errorf(\"nil restore request\")\n\t}\n\n\t// This is a minor inconvenience while using the incremental restore API that\n\t// when incrementalFrom is set to 1, we throw an error back. The restore API\n\t// takes two backup numbers incrementalFrom & backupNum and restores all the\n\t// backups including both the ends, i.e. following set notation all the backups\n\t// in the set [incrementalFrom, backupNum] are restored. This should work fine\n\t// when incrementalFrom is set to 1 which is a full backup.\n\tif req.IncrementalFrom == 1 {\n\t\treq.IncrementalFrom = 0\n\t}\n\n\t// Clean up the cluster if it is a full backup restore.\n\tif req.IncrementalFrom == 0 {\n\t\t// Drop all the current data. This also cancels all existing transactions.\n\t\tdropProposal := pb.Proposal{\n\t\t\tMutations: &pb.Mutations{\n\t\t\t\tGroupId: req.GroupId,","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/dgraph-io/dgraph/blob/759e242be62c91f8d084da06ad0c8d21256d9c07/worker/online_restore.go#L183-L219","documentation":"Returned by handleRestoreProposal (worker/online_restore.go:201) when the Restore proposal carried through Raft has a nil RestoreRequest. The proposal is applied on every node of the group via applyCommitted, and this internal invariant guard means the proposal payload was empty or corrupted rather than a user error.","triggerScenarios":"A pb.Proposal with Restore field unset is proposed to the Raft group; a serialization/upgrade mismatch between node versions causes the Restore field to decode as nil.","commonSituations":"Rolling upgrades where an older/newer Dgraph binary decodes the proposal differently; custom tooling or tests proposing malformed proposals; memory corruption bugs in the proposal path.","solutions":["Verify all Alphas in the cluster run the same Dgraph version and finish any rolling upgrade before restoring.","Retry the restore; a transient decode issue should not recur on a healthy, homogeneous cluster.","Collect Alpha logs around the proposal index and report upstream if it reproduces on a single-version cluster."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Ensure homogeneous binary versions before proposing restores\n// (run on every Alpha)\ndgraph version --full   # all nodes must match exactly","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep all cluster nodes on the exact same Dgraph version.","Complete rolling upgrades fully before issuing restore proposals.","Do not construct pb.Proposal messages manually; use the restore API.","If reproducible on a homogeneous cluster, file an upstream bug with logs."],"tags":["dgraph","restore","raft","internal"],"backgroundTag":"nil-proposal-payload","analyzedSha":"759e242be62c91f8d084da06ad0c8d21256d9c07","analyzedAt":"2026-09-01T14:42:12.034Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}