{"record":{"id":"785443f2c09c706e","repo":"vitessio/vitess","slug":"w-cannot-delete-workflow-in-s","errorCode":null,"errorMessage":"%w: cannot delete workflow in %s","messagePattern":"%w: cannot delete workflow in (.+?)","errorType":"error_code","errorClass":"errors.ErrUnauthorized","httpStatus":null,"severity":"error","filePath":"go/vt/vtadmin/api.go","lineNumber":2839,"sourceCode":"\tresponse, err := vte.ExplainsAsText(plans)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error converting vtexplain to text output: %w\", err)\n\t}\n\n\treturn &vtadminpb.VTExplainResponse{\n\t\tResponse: response,\n\t}, nil\n}\n\n// WorkflowDelete is part of the vtadminpb.VTAdminServer interface.\nfunc (api *API) WorkflowDelete(ctx context.Context, req *vtadminpb.WorkflowDeleteRequest) (*vtctldatapb.WorkflowDeleteResponse, error) {\n\tspan, ctx := trace.NewSpan(ctx, \"API.WorkflowDelete\")\n\tdefer span.Finish()\n\n\tspan.Annotate(\"cluster_id\", req.ClusterId)\n\n\tif !api.authz.IsAuthorized(ctx, req.ClusterId, rbac.WorkflowResource, rbac.DeleteAction) {\n\t\treturn nil, fmt.Errorf(\"%w: cannot delete workflow 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// Set the default options which are not supported in VTAdmin Web.\n\treturn c.Vtctld.WorkflowDelete(ctx, req.Request)\n}\n\n// WorkflowSwitchTraffic is part of the vtadminpb.VTAdminServer interface.\nfunc (api *API) WorkflowSwitchTraffic(ctx context.Context, req *vtadminpb.WorkflowSwitchTrafficRequest) (*vtctldatapb.WorkflowSwitchTrafficResponse, error) {\n\tspan, ctx := trace.NewSpan(ctx, \"API.WorkflowSwitchTraffic\")\n\tdefer span.Finish()\n\n\tspan.Annotate(\"cluster_id\", req.ClusterId)\n","sourceCodeStart":2821,"sourceCodeEnd":2857,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtadmin/api.go#L2821-L2857","documentation":"The WorkflowDelete RPC checks RBAC authorization before deleting a workflow. If the caller's authenticated identity lacks DeleteAction permission on the WorkflowResource for the given cluster, the request fails with errors.ErrUnauthorized wrapped with the cluster ID. This is an access-control rejection, not a workflow-state failure.","triggerScenarios":"Calling WorkflowDelete (API.GenerateWorkflowDeleteResponse) with req.ClusterId set to a cluster where the caller's RBAC rules do not grant workflow delete permission — e.g. rules limited to read actions or a different cluster ID.","commonSituations":"Read-only vtadmin user attempting a destructive operation; RBAC policy file grants permissions on cluster 'prod' but user targets 'staging' (or vice versa); missing 'delete' action in the role's rules; authz middleware not extracting identity, causing default-deny.","solutions":["Update vtadmin's RBAC rules to grant the caller's role 'delete' on the workflow resource for that cluster ID.","Confirm req.ClusterId matches the cluster ID used in the RBAC policy (exact string match).","Authenticate with credentials/identity that map to an authorized role (check the identity provider and vtadmin's auth setup).","Inspect the RBAC config file (`--rbac` JSON) rules array and add e.g. {\"resource\": \"workflow\", \"actions\": [\"delete\"], \"clusters\": [\"<clusterId>\"]}.","If you believe access should be granted, verify with `vtadmin` logs how the IsAuthorized decision was reached."],"exampleFix":"// before: vtadmin rbac config\n[{\"resource\": \"workflow\", \"actions\": [\"get\"], \"clusters\": [\"*\"]}]\n// after\n[{\"resource\": \"workflow\", \"actions\": [\"get\", \"delete\"], \"clusters\": [\"*\"]}]","handlingStrategy":"try-catch","validationCode":"// Client-side pre-check against the published RBAC policy\nif !rbacPolicy.Allows(identity, rbac.WorkflowResource, rbac.DeleteAction, clusterID) {\n    return fmt.Errorf(\"identity %s may not delete workflows in cluster %s\", identity, clusterID)\n}","typeGuard":"func authorized(id Identity, res rbac.Resource, act rbac.Action, cluster string) bool {\n    for _, r := range id.Roles {\n        for _, rule := range r.Rules {\n            if rule.Matches(res, act, cluster) { return true }\n        }\n    }\n    return false\n}","tryCatchPattern":"resp, err := api.WorkflowDelete(ctx, req)\nif err != nil && strings.Contains(err.Error(), \"cannot delete workflow\") {\n    return vterrors.Errorf(vtrpcpb.Code_PERMISSION_DENIED,\n        \"insufficient RBAC permissions to delete workflow in cluster %s\", req.ClusterId)\n}","preventionTips":["Grant the delete action on the workflow resource to the roles that need it, scoped to the correct cluster IDs.","Hide destructive workflow actions in the UI unless the user's role allows delete.","Keep cluster IDs in the RBAC policy in exact sync with cluster config IDs.","Review RBAC config during role changes so delete permissions are deliberate."],"tags":["vtadmin","rbac","authorization","workflow"],"backgroundTag":"rbac-unauthorized","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}