vitessio/vitess · error · ErrUnauthorized

%w: cannot complete schema migration in %s

Error message

%w: cannot complete schema migration in %s

What it means

VTAdmin's CompleteSchemaMigration RPC returns this when RBAC denies the caller the 'complete' action on the SchemaMigration resource for the requested cluster. The authorization gate runs first and wraps errors.ErrUnauthorized. It means policy, not the migration itself, blocked the call.

Source

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

	}

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

	return c.CleanupSchemaMigration(ctx, req.Request)
}

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

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

	if !api.authz.IsAuthorized(ctx, req.ClusterId, rbac.SchemaMigrationResource, rbac.CompleteSchemaMigrationAction) {
		return nil, fmt.Errorf("%w: cannot complete schema migration in %s", errors.ErrUnauthorized, req.ClusterId)
	}

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

	return c.CompleteSchemaMigration(ctx, req.Request)
}

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

	if !api.authz.IsAuthorized(ctx, req.ClusterId, rbac.ClusterResource, rbac.GetAction) {
		return nil, nil
	}

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Add 'complete' to the schema-migration resource actions for the caller's role in the RBAC config
  2. Verify the ClusterId in the request matches a role the caller holds
  3. Restart vtadmin to pick up RBAC changes

Example fix

// before
  - resource: schema-migration
    actions: [get]
// after
  - resource: schema-migration
    actions: [get, create, complete]
Defensive patterns

Strategy: validation

Validate before calling

const canComplete = permissions.some(rule => rule.resource === 'schema-migration' && (rule.actions.includes('complete') || rule.actions.includes('*')));
if (!canComplete) throw new Error('missing complete permission for schema-migration');

Type guard

function isCompleteDenied(err: unknown): boolean {
  return err instanceof Error && err.message.includes('cannot complete schema migration');
}

Try / catch

try {
  await completeSchemaMigration(clusterId, uuid);
} catch (err) {
  if (String(err).includes('cannot complete schema migration')) {
    requestGrant('schema-migration:complete', clusterId);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling CompleteSchemaMigration (POST /schema/complete) for a cluster where the caller's role does not include the complete action.

Common situations: Finalizing a successful VReplication-based migration with a mid-tier admin role that allows get/create but not complete; hand-written RBAC configs missing newer actions; requesting the wrong cluster ID.

Related errors


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