vitessio/vitess · error · ErrUnauthorized

%w: cannot delete shards in %s

Error message

%w: cannot delete shards in %s

What it means

VTAdmin's DeleteShards RPC returns this when RBAC denies the caller the 'delete' action on the Shard resource in the requested cluster. The API fails fast before contacting the cluster, wrapping errors.ErrUnauthorized. Shard deletion is deliberately gated separately from shard creation.

Source

Thrown at go/vt/vtadmin/api.go:672

	}

	c, err := api.getClusterForRequest(req.ClusterId)
	if err != nil {
		return nil, err
	}

	return c.DeleteKeyspace(ctx, req.Options)
}

// DeleteShards is part of the vtadminpb.VTAdminServer interface.
func (api *API) DeleteShards(ctx context.Context, req *vtadminpb.DeleteShardsRequest) (*vtctldatapb.DeleteShardsResponse, error) {
	span, ctx := trace.NewSpan(ctx, "API.DeleteShards")
	defer span.Finish()

	span.Annotate("cluster_id", req.ClusterId)

	if !api.authz.IsAuthorized(ctx, req.ClusterId, rbac.ShardResource, rbac.DeleteAction) {
		return nil, fmt.Errorf("%w: cannot delete shards in %s", errors.ErrUnauthorized, req.ClusterId)
	}

	c, err := api.getClusterForRequest(req.ClusterId)
	if err != nil {
		return nil, err
	}

	return c.DeleteShards(ctx, req.Options)
}

// DeleteTablet is part of the vtadminpb.VTAdminServer interface.
func (api *API) DeleteTablet(ctx context.Context, req *vtadminpb.DeleteTabletRequest) (*vtadminpb.DeleteTabletResponse, error) {
	span, ctx := trace.NewSpan(ctx, "API.DeleteTablet")
	defer span.Finish()

	tablet, c, err := api.getTabletForAction(ctx, span, rbac.DeleteAction, req.Alias, req.ClusterIds)
	if err != nil {
		return nil, err

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Add 'delete' to the shard resource actions for the caller's role in the RBAC config
  2. Ensure the role's cluster scope includes the target cluster ID (wildcard if needed)
  3. Restart vtadmin so the updated RBAC policy is loaded

Example fix

// before
  - resource: shard
    actions: [get, create]
// after
  - resource: shard
    actions: [get, create, delete]
Defensive patterns

Strategy: validation

Validate before calling

const canDeleteShards = permissions.some(rule => rule.resource === 'shard' && (rule.actions.includes('delete') || rule.actions.includes('*')) && (rule.clusters.includes(clusterId) || rule.clusters.includes('*')));
if (!canDeleteShards) throw new Error('RBAC denies shard delete in ' + clusterId);

Type guard

function isShardDeleteDenied(err: unknown): boolean {
  return err instanceof Error && err.message.includes('cannot delete shards');
}

Try / catch

try {
  await deleteShards(clusterId, shards);
} catch (err) {
  if (String(err).includes('cannot delete shards')) {
    escalateToAdmin('shard:delete grant needed for ' + clusterId);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling DeleteShards (DELETE /shards) for a cluster where the caller's role omits shard-delete permission.

Common situations: Decommissioning workflows run by service accounts lacking delete grants; RBAC configs granting wildcard on keyspaces but not shards; multi-cluster setups where the role covers only some cluster IDs.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/6e41be5e6d02a818. Report an issue: GitHub.