hashicorp/nomad · error

snapshot cannot be nil

Error message

snapshot cannot be nil

What it means

CreateSnapshot returns this when an element of args.Snapshots is nil. The loop iterates over all requested snapshots and returns immediately (deliberately not appending to the multierror, per the inline comment, because the request state is 'weird') if any entry is nil. It is a caller-side payload validation failure of the CreateSnapshot RPC.

Source

Thrown at nomad/csi_endpoint.go:1614

	aclObj, err := v.srv.ResolveACL(args)
	if err != nil {
		return err
	}
	if !allowVolume(aclObj, args.RequestNamespace()) || !aclObj.AllowPluginRead() {
		return structs.ErrPermissionDenied
	}

	state, err := v.srv.fsm.State().Snapshot()
	if err != nil {
		return err
	}

	method := "ClientCSI.ControllerCreateSnapshot"
	var mErr multierror.Error
	for _, snap := range args.Snapshots {
		if snap == nil {
			// we intentionally don't multierror here because we're in a weird state
			return fmt.Errorf("snapshot cannot be nil")
		}

		vol, err := state.CSIVolumeByID(nil, args.RequestNamespace(), snap.SourceVolumeID)
		if err != nil {
			multierror.Append(&mErr, fmt.Errorf("error querying volume %q: %v", snap.SourceVolumeID, err))
			continue
		}
		if vol == nil {
			multierror.Append(&mErr, fmt.Errorf("no such volume %q", snap.SourceVolumeID))
			continue
		}

		pluginID := snap.PluginID
		if pluginID == "" {
			pluginID = vol.PluginID
		}

		plugin, err := state.CSIPluginByID(nil, pluginID)

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Fix the caller to only populate non-nil snapshot entries before sending CSISnapshotCreateRequest
  2. Check the code that builds args.Snapshots for a mismatch between slice length and initialized elements (pre-sized slice with partial appends)
  3. Validate/de-duplicate the payload server-side-adjacent logic: filter nil entries before calling CreateSnapshot
  4. If the request came from JSON, fix the payload so no `null` elements appear in the Snapshots array

Example fix

// before
snaps := make([]*structs.CSISnapshot, 3)
snaps[0] = ...; snaps[1] = ... // snaps[2] stays nil
// after
snaps := []*structs.CSISnapshot{}
for _, s := range desired { if s != nil { snaps = append(snaps, s) } }
Defensive patterns

Strategy: validation

Validate before calling

func sanitizeSnapshots(in []*structs.CSISnapshot) []*structs.CSISnapshot {
    out := in[:0]
    for _, s := range in { if s != nil && s.SourceVolumeID != "" { out = append(out, s) } }
    return out
}

Type guard

func validSnapshot(s *structs.CSISnapshot) bool { return s != nil && s.SourceVolumeID != "" }

Try / catch

if err := createSnapshot(req); err != nil {
    if strings.Contains(err.Error(), "snapshot cannot be nil") {
        return fmt.Errorf("request contained nil snapshot entry; fix payload: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: A caller (UI, API client, or job spec tooling) submits a CSISnapshotCreateRequest whose Snapshots slice contains a nil *CSISnapshot entry — e.g. constructing the slice with a fixed length and leaving one index unset, or unmarshalling JSON array entries of `null`.

Common situations: Batch snapshot scripts that append only some snapshots to a pre-sized slice (`make([]*structs.CSISnapshot, n)` and a partial loop); JSON payloads like `{"Snapshots":[null]}`; a deserialization bug or an upstream wrapper that passes nil through for skipped entries.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/c69a00b04a5b8321. Report an issue: GitHub.