{"record":{"id":"d8a1008c4b40cbf9","repo":"dgraph-io/dgraph","slug":"updateextsnapshotstreamingstaterequest-cannot-have","errorCode":null,"errorMessage":"UpdateExtSnapshotStreamingStateRequest cannot have both Start and Finish set to true","messagePattern":"UpdateExtSnapshotStreamingStateRequest cannot have both Start and Finish set to true","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"edgraph/server.go","lineNumber":2074,"sourceCode":"\n\tif req == nil {\n\t\treturn nil, errors.New(\"UpdateExtSnapshotStreamingStateRequest must not be nil\")\n\t}\n\n\t// External-snapshot import is a destructive admin operation: it arms import mode and\n\t// (via StreamExtSnapshot) replaces a group store. Gate it on both authorization paths so\n\t// it is protected under ACL and under an --security auth-token. Each gate fails open when\n\t// its feature is unconfigured, so the arming requirement on the stream path backstops the\n\t// bare-OSS case.\n\tif err := AuthorizeGuardians(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := hasPoormansAuth(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif req.Start && req.Finish {\n\t\treturn nil, errors.New(\"UpdateExtSnapshotStreamingStateRequest cannot have both Start and Finish set to true\")\n\t}\n\n\tgroups, err := worker.ProposeDrain(ctx, req)\n\tif err != nil {\n\t\tglog.Errorf(\"[import] failed to propose drain mode: %v\", err)\n\t\treturn nil, err\n\t}\n\n\tresp := &api.UpdateExtSnapshotStreamingStateResponse{Groups: groups}\n\n\treturn resp, nil\n}\n\nfunc (s *Server) StreamExtSnapshot(stream api.Dgraph_StreamExtSnapshotServer) error {\n\tdefer x.ExtSnapshotStreamingState(false)\n\n\t// Authorize at stream start, before any data is consumed. Stream auth metadata rides on the\n\t// stream's context, so the same gates used for the unary entry point apply here.","sourceCodeStart":2056,"sourceCodeEnd":2092,"githubUrl":"https://github.com/dgraph-io/dgraph/blob/759e242be62c91f8d084da06ad0c8d21256d9c07/edgraph/server.go#L2056-L2092","documentation":"The UpdateExtSnapshotStreamingState request uses Start and Finish as mutually exclusive state-transition flags: Start arms external-snapshot import/drain mode, Finish ends it. Setting both to true is contradictory, so the server rejects it after auth checks (edgraph/server.go:2074). Only one flag may be set per call — issue two sequential calls to arm and then finish.","triggerScenarios":"Calling UpdateExtSnapshotStreamingState with both req.Start=true and req.Finish=true in the same message — e.g. a client reusing a populated struct and flipping both fields, or constructing the request from ambiguous user input.","commonSituations":"Admin tooling with checkbox UIs that allow selecting both actions; retry logic that sets Finish on a request originally built for Start; clients built against different API versions where flag semantics changed.","solutions":["Send Start=true (Finish unset) to begin snapshot streaming, then a separate call with Finish=true (Start unset) to end it","Add client-side validation that exactly one of Start/Finish is true before invoking the RPC","If a struct is reused, explicitly reset the opposite flag each call","Check for SDK/tooling updates if your client builds requests with both flags by default"],"exampleFix":"// before\nreq := &api.UpdateExtSnapshotStreamingStateRequest{Start: true, Finish: true}\n// after\nreq := &api.UpdateExtSnapshotStreamingStateRequest{Start: true}\n// ... later, to finish:\nfinishReq := &api.UpdateExtSnapshotStreamingStateRequest{Finish: true}","handlingStrategy":"validation","validationCode":"func validExtSnapFlags(r *api.UpdateExtSnapshotStreamingStateRequest) bool {\n\treturn r != nil && (r.Start != r.Finish) // exactly one set\n}\n// guard before RPC:\n// if !validExtSnapFlags(req) { return errors.New(\"set exactly one of Start/Finish\") }","typeGuard":"func isExclusiveStartFinish(r *api.UpdateExtSnapshotStreamingStateRequest) bool {\n\treturn r != nil && r.Start != r.Finish\n}","tryCatchPattern":"resp, err := client.UpdateExtSnapshotStreamingState(ctx, req)\nif err != nil && strings.Contains(err.Error(), \"both Start and Finish\") {\n\t// fix request flags: send separate Start and Finish calls\n}","preventionTips":["Issue Start and Finish as two separate RPC calls","Validate exactly-one-flag-set before sending admin requests","Reset opposite flags when reusing request structs","Model the lifecycle as a state machine (idle -> started -> finished) in tooling"],"tags":["dgraph","grpc","admin","validation"],"backgroundTag":"mutually-exclusive-request-flags","analyzedSha":"759e242be62c91f8d084da06ad0c8d21256d9c07","analyzedAt":"2026-09-01T14:42:12.034Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}