hashicorp/nomad · error

snapshot %q could not be deleted because it is in use: %v

Error message

snapshot %q could not be deleted because it is in use: %v

What it means

ControllerDeleteSnapshot translates gRPC codes.FailedPrecondition from the plugin's DeleteSnapshot RPC per the CSI spec: the snapshot exists but cannot be deleted because something depends on it (e.g. a volume created/restored from it, or an in-progress clone). Nomad surfaces this as 'in use' so the operator knows the delete is blocked by a dependency, not a transient fault.

Source

Thrown at plugins/csi/client.go:694

	if err := c.ensureConnected(ctx); err != nil {
		return err
	}

	err := req.Validate()
	if err != nil {
		return err
	}
	creq := req.ToCSIRepresentation()
	_, err = c.controllerClient.DeleteSnapshot(ctx, creq, opts...)

	// these standard gRPC error codes are overloaded with CSI-specific
	// meanings, so translate them into user-understandable terms
	// https://github.com/container-storage-interface/spec/blob/master/spec.md#deletesnapshot-errors
	if err != nil {
		code := status.Code(err)
		switch code {
		case codes.FailedPrecondition:
			return fmt.Errorf(
				"snapshot %q could not be deleted because it is in use: %v",
				req.SnapshotID, err)
		case codes.Aborted:
			return fmt.Errorf("snapshot %q has a pending operation: %v", req.SnapshotID, err)
		case codes.Internal:
			return fmt.Errorf(
				"controller plugin returned an internal error, check the plugin allocation logs for more information: %v", err)
		}
		return err
	}

	return nil
}

func (c *client) ControllerListSnapshots(ctx context.Context, req *ControllerListSnapshotsRequest, opts ...grpc.CallOption) (*ControllerListSnapshotsResponse, error) {
	if err := c.ensureConnected(ctx); err != nil {
		return nil, err
	}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Find and delete (or finish) the dependent volumes/restores created from this snapshot before retrying.
  2. Reorder the cleanup workflow: delete dependent volumes first, then the snapshot.
  3. Exclude in-use snapshots from the retention/pruning list (check snapshot dependencies via backend tooling).
  4. Confirm req.SnapshotID is correct — a stale/wrong ID may point at a snapshot that is genuinely in use.

Example fix

// before: blind prune
for _, s := range snapshots { deleteSnapshot(s.ID) }
// after: skip snapshots with dependents
for _, s := range snapshots {
  if !hasDependentVolumes(s.ID) { deleteSnapshot(s.ID) }
}
Defensive patterns

Strategy: validation

Validate before calling

func canDeleteSnapshot(ctx context.Context, c *client, snapID string) error {
  vols, err := listVolumesSourcedFromSnapshot(ctx, c, snapID) // backend/driver API
  if err != nil { return err }
  if len(vols) > 0 {
    return fmt.Errorf("snapshot %s in use by %d volumes", snapID, len(vols))
  }
  return nil
}

Try / catch

err := c.ControllerDeleteSnapshot(ctx, req)
if err != nil && strings.Contains(err.Error(), "in use") {
  log.Printf("deferring delete of %s: dependent resources exist", req.SnapshotID)
  return ErrSnapshotInUse // handle in a later cleanup pass
}

Prevention

When it happens

Trigger: Calling ControllerDeleteSnapshot with req.SnapshotID for a snapshot that is the source of an existing volume, an active restore, or a dependent clone, and the plugin returns codes.FailedPrecondition (client.go:694).

Common situations: Trying to prune snapshots still referenced by running CSI volumes; a restore job is consuming the snapshot; retention policy deleting snapshots linked to downstream copies on the array; cloud provider blocks deleting a snapshot used by another disk.

Related errors


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