{"record":{"id":"04f36a1a522c3143","repo":"vitessio/vitess","slug":"w-cannot-create-schema-migration-in-s","errorCode":null,"errorMessage":"%w: cannot create schema migration in %s","messagePattern":"%w: cannot create schema migration in (.+?)","errorType":"validation","errorClass":"ErrUnauthorized","httpStatus":null,"severity":"error","filePath":"go/vt/vtadmin/api.go","lineNumber":486,"sourceCode":"\n\t// Maintain order of clusters when removing dynamic cluster\n\tclusterIndex := stdsort.Search(len(api.clusters), func(i int) bool { return api.clusters[i].ID == key })\n\tif clusterIndex >= len(api.clusters) || clusterIndex < 0 {\n\t\tlog.Error(fmt.Sprintf(\"Cannot remove cluster %s from api.clusters. Cluster index %d is out of range for clusters slice of %d length.\", key, clusterIndex, len(api.clusters)))\n\t}\n\n\tapi.clusters = append(api.clusters[:clusterIndex], api.clusters[clusterIndex+1:]...)\n}\n\n// ApplySchema is part of the vtadminpb.VTAdminServer interface.\nfunc (api *API) ApplySchema(ctx context.Context, req *vtadminpb.ApplySchemaRequest) (*vtctldatapb.ApplySchemaResponse, error) {\n\tspan, ctx := trace.NewSpan(ctx, \"API.ApplySchema\")\n\tdefer span.Finish()\n\n\tspan.Annotate(\"cluster_id\", req.ClusterId)\n\n\tif !api.authz.IsAuthorized(ctx, req.ClusterId, rbac.SchemaMigrationResource, rbac.CreateAction) {\n\t\treturn nil, fmt.Errorf(\"%w: cannot create 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\t// Parser with default options. New() itself initializes with default MySQL version.\n\tparser, err := sqlparser.New(sqlparser.Options{\n\t\tTruncateUILen:  512,\n\t\tTruncateErrLen: 0,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Split the sql statement received from request.\n\tsqlParts, err := parser.SplitStatementToPieces(req.Sql)","sourceCodeStart":468,"sourceCodeEnd":504,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtadmin/api.go#L468-L504","documentation":"VTAdmin's ApplySchema RPC returns this when the authenticated caller lacks the RBAC 'create' action on the SchemaMigration resource for the requested cluster. The API checks authorization before touching the cluster and fails fast with a wrapped errors.ErrUnauthorized. It is a policy decision, not an infrastructure failure.","triggerScenarios":"Calling POST /schema/apply (ApplySchema) with a ClusterId whose RBAC role for the caller does not include create permission on schema migrations.","commonSituations":"Users whose RBAC config grants read-only or schema-read roles attempting to apply DDL via vtadmin; misconfigured rbac config YAML missing the schema-migration resource/action mapping; passing the wrong cluster ID so the matched role denies the action.","solutions":["Update the caller's role in the vtadmin RBAC config to include action 'create' on the schema-migration resource for the target cluster","Verify the cluster ID in the request matches the cluster the role is scoped to (use wildcards like '*' if intended)","Restart vtadmin after editing the RBAC config and re-authenticate so new permissions take effect"],"exampleFix":"// before (rbac.yaml role)\nrules:\n  - resource: schema-migration\n    actions: [get]\n// after\nrules:\n  - resource: schema-migration\n    actions: [get, create]","handlingStrategy":"validation","validationCode":"const canApply = await fetch('/api/permissions').then(r => r.json()).then(p => p.some(rule => rule.resource === 'schema-migration' && (rule.actions.includes('create') || rule.actions.includes('*')) && (rule.clusters.includes(clusterId) || rule.clusters.includes('*'))));\nif (!canApply) throw new Error('not authorized to create schema migration in ' + clusterId);","typeGuard":"function isUnauthorized(err: unknown): err is Error {\n  return err instanceof Error && err.message.includes('cannot create schema migration');\n}","tryCatchPattern":"try {\n  await applySchema(clusterId, sql);\n} catch (err) {\n  if (String(err).includes('cannot create schema migration')) {\n    notifyAdminForRbacGrant(clusterId);\n  } else {\n    throw err;\n  }\n}","preventionTips":["Check the vtadmin RBAC config for your role before automating schema operations","Scope roles per cluster and verify the cluster ID you pass matches","Re-authenticate after any RBAC change"],"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"}