dgraph-io/dgraph · error

UpdateExtSnapshotStreamingStateRequest cannot have both Star

Error message

UpdateExtSnapshotStreamingStateRequest cannot have both Start and Finish set to true

What it means

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.

Source

Thrown at edgraph/server.go:2074

	if req == nil {
		return nil, errors.New("UpdateExtSnapshotStreamingStateRequest must not be nil")
	}

	// External-snapshot import is a destructive admin operation: it arms import mode and
	// (via StreamExtSnapshot) replaces a group store. Gate it on both authorization paths so
	// it is protected under ACL and under an --security auth-token. Each gate fails open when
	// its feature is unconfigured, so the arming requirement on the stream path backstops the
	// bare-OSS case.
	if err := AuthorizeGuardians(ctx); err != nil {
		return nil, err
	}
	if err := hasPoormansAuth(ctx); err != nil {
		return nil, err
	}

	if req.Start && req.Finish {
		return nil, errors.New("UpdateExtSnapshotStreamingStateRequest cannot have both Start and Finish set to true")
	}

	groups, err := worker.ProposeDrain(ctx, req)
	if err != nil {
		glog.Errorf("[import] failed to propose drain mode: %v", err)
		return nil, err
	}

	resp := &api.UpdateExtSnapshotStreamingStateResponse{Groups: groups}

	return resp, nil
}

func (s *Server) StreamExtSnapshot(stream api.Dgraph_StreamExtSnapshotServer) error {
	defer x.ExtSnapshotStreamingState(false)

	// Authorize at stream start, before any data is consumed. Stream auth metadata rides on the
	// stream's context, so the same gates used for the unary entry point apply here.

View on GitHub (pinned to 759e242be6)

Solutions

  1. Send Start=true (Finish unset) to begin snapshot streaming, then a separate call with Finish=true (Start unset) to end it
  2. Add client-side validation that exactly one of Start/Finish is true before invoking the RPC
  3. If a struct is reused, explicitly reset the opposite flag each call
  4. Check for SDK/tooling updates if your client builds requests with both flags by default

Example fix

// before
req := &api.UpdateExtSnapshotStreamingStateRequest{Start: true, Finish: true}
// after
req := &api.UpdateExtSnapshotStreamingStateRequest{Start: true}
// ... later, to finish:
finishReq := &api.UpdateExtSnapshotStreamingStateRequest{Finish: true}
Defensive patterns

Strategy: validation

Validate before calling

func validExtSnapFlags(r *api.UpdateExtSnapshotStreamingStateRequest) bool {
	return r != nil && (r.Start != r.Finish) // exactly one set
}
// guard before RPC:
// if !validExtSnapFlags(req) { return errors.New("set exactly one of Start/Finish") }

Type guard

func isExclusiveStartFinish(r *api.UpdateExtSnapshotStreamingStateRequest) bool {
	return r != nil && r.Start != r.Finish
}

Try / catch

resp, err := client.UpdateExtSnapshotStreamingState(ctx, req)
if err != nil && strings.Contains(err.Error(), "both Start and Finish") {
	// fix request flags: send separate Start and Finish calls
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01). Data as JSON: /api/errors/d8a1008c4b40cbf9. Report an issue: GitHub.