{"record":{"id":"e27d9e750e9ac49b","repo":"vitessio/vitess","slug":"w-cannot-retry-schema-migration-in-s","errorCode":null,"errorMessage":"%w: cannot retry schema migration in %s","messagePattern":"%w: cannot retry schema migration in (.+?)","errorType":"validation","errorClass":"ErrUnauthorized","httpStatus":null,"severity":"error","filePath":"go/vt/vtadmin/api.go","lineNumber":2324,"sourceCode":"\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &vtadminpb.ReloadSchemaShardResponse{\n\t\tEvents: res.Events,\n\t}, nil\n}\n\n// RetrySchemaMigration is part of the vtadminpb.VTAdminServer interface.\nfunc (api *API) RetrySchemaMigration(ctx context.Context, req *vtadminpb.RetrySchemaMigrationRequest) (*vtctldatapb.RetrySchemaMigrationResponse, error) {\n\tspan, ctx := trace.NewSpan(ctx, \"API.RetrySchemaMigration\")\n\tdefer span.Finish()\n\n\tspan.Annotate(\"cluster_id\", req.ClusterId)\n\n\tif !api.authz.IsAuthorized(ctx, req.ClusterId, rbac.SchemaMigrationResource, rbac.RetryAction) {\n\t\treturn nil, fmt.Errorf(\"%w: cannot retry schema migration in %s\", errors.ErrUnauthorized, req.ClusterId)\n\t}\n\n\tc, err := api.getClusterForRequest(req.ClusterId)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn c.RetrySchemaMigration(ctx, req.Request)\n}\n\n// RunHealthCheck is part of the vtadminpb.VTAdminServer interface.\nfunc (api *API) RunHealthCheck(ctx context.Context, req *vtadminpb.RunHealthCheckRequest) (*vtadminpb.RunHealthCheckResponse, error) {\n\tspan, ctx := trace.NewSpan(ctx, \"API.RunHealthCheck\")\n\tdefer span.Finish()\n\n\ttablet, c, err := api.getTabletForAction(ctx, span, rbac.GetAction, req.Alias, req.ClusterIds)\n\tif err != nil {\n\t\treturn nil, err","sourceCodeStart":2306,"sourceCodeEnd":2342,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtadmin/api.go#L2306-L2342","documentation":"VTAdmin's RetrySchemaMigration RPC returns this when RBAC denies the retry action on the schema_migration resource in the target cluster. It wraps errors.ErrUnauthorized for errors.Is matching. It is returned before contacting vtctld.","triggerScenarios":"Calling API.RetrySchemaMigration when api.authz.IsAuthorized(ctx, req.ClusterId, rbac.SchemaMigrationResource, rbac.RetryAction) returns false.","commonSituations":"RBAC role has get/list on schema migrations but not retry; users trying to retry a failed migration with a viewer role; misconfigured subject mapping falling back to a restricted role.","solutions":["Grant the retry action on the schema_migration resource to the caller's RBAC role","Confirm the cluster_id in the request matches the rules configured for that role","Verify authenticated identity resolution (auth headers/Impersonation rules) maps to the intended subject"],"exampleFix":"// before\n- resource: schema_migration\n  actions: [get]\n// after\n- resource: schema_migration\n  actions: [get, retry]","handlingStrategy":"validation","validationCode":"func canRetrySchemaMigration(role string) bool {\n    allowed := map[string]bool{\"admin\": true, \"operator\": true}\n    return allowed[role]\n}","typeGuard":"func isUnauthorizedErr(err error) bool {\n    return err != nil && errors.Is(err, apierrors.ErrUnauthorized)\n}","tryCatchPattern":"resp, err := client.RetrySchemaMigration(ctx, req)\nif err != nil {\n    if errors.Is(err, apierrors.ErrUnauthorized) {\n        return fmt.Errorf(\"insufficient RBAC permissions to retry schema migration in %s\", req.ClusterId)\n    }\n    return err\n}","preventionTips":["Grant schema_migration retry alongside create in operator roles so migration workflows are fully operable","Verify the caller identity maps to the expected RBAC subject","Test retry permissions for each cluster ID in staging"],"tags":["rbac","authorization","vtadmin","schema-migration"],"backgroundTag":"unauthorized-rbac-action","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}