{"record":{"id":"6b81c555110cb0aa","repo":"vitessio/vitess","slug":"cannot-delete-workflow-because-you-have-already-sw","errorCode":null,"errorMessage":"cannot delete workflow because you have already switched write traffic","messagePattern":"cannot delete workflow because you have already switched write traffic","errorType":"error_code","errorClass":"ErrWorkflowDeleteWritesSwitched","httpStatus":null,"severity":"error","filePath":"go/vt/vtctl/workflow/server.go","lineNumber":128,"sourceCode":"\tlockTablesCycleDelay = time.Duration(100 * time.Millisecond)\n\n\tSqlUnfreezeWorkflow = \"update _vt.vreplication set state='Running', message='' where db_name=%a and workflow=%a\"\n)\n\nvar (\n\t// ErrInvalidWorkflow is a catchall error type for conditions that should be\n\t// impossible when operating on a workflow.\n\tErrInvalidWorkflow = errors.New(\"invalid workflow\")\n\t// ErrMultipleSourceKeyspaces occurs when a workflow somehow has multiple\n\t// source keyspaces across different shard primaries. This should be\n\t// impossible.\n\tErrMultipleSourceKeyspaces = errors.New(\"multiple source keyspaces for a single workflow\")\n\t// ErrMultipleTargetKeyspaces occurs when a workflow somehow has multiple\n\t// target keyspaces across different shard primaries. This should be\n\t// impossible.\n\tErrMultipleTargetKeyspaces          = errors.New(\"multiple target keyspaces for a single workflow\")\n\tErrWorkflowCompleteNotFullySwitched = errors.New(\"cannot complete workflow because you have not yet switched all read and write traffic\")\n\tErrWorkflowDeleteWritesSwitched     = errors.New(\"cannot delete workflow because you have already switched write traffic\")\n)\n\n// Server provides an API to work with Vitess workflows, like vreplication\n// workflows (MoveTables, Reshard, etc) and schema migration workflows.\ntype Server struct {\n\tts  *topo.Server\n\ttmc tmclient.TabletManagerClient\n\t// Limit the number of concurrent background goroutines if needed.\n\tsem     *semaphore.Weighted\n\tenv     *vtenv.Environment\n\toptions serverOptions\n}\n\n// NewServer returns a new server instance with the given topo.Server and\n// TabletManagerClient.\nfunc NewServer(env *vtenv.Environment, ts *topo.Server, tmc tmclient.TabletManagerClient, opts ...ServerOption) *Server {\n\ts := &Server{\n\t\tts:  ts,","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtctl/workflow/server.go#L110-L146","documentation":"WorkflowDelete (and TestWorkflowDelete) refuses to delete a MoveTables workflow after write traffic has already been switched to the target, because deleting it would drop the source tables' replication safety net / reverse replication. ErrWorkflowDeleteWritesSwitched is the sentinel for this guarded state.","triggerScenarios":"Calling WorkflowDelete on a workflow where SwitchWrites has completed (writes are now on the target keyspace) and the source tables would be removed or replication torn down unsafely.","commonSituations":"Operators cleaning up a finished MoveTables migration who call the plain delete command instead of `MoveTablesComplete`/DropSource with the intended table-removal behavior.","solutions":["Use `MoveTablesComplete` (or `WorkflowDelete` with the appropriate drop-source/table-removal options) instead of a plain delete after writes are switched.","If you truly must remove the workflow, explicitly pass the drop-source / table removal type that documents the intent.","Roll back write traffic (reverse SwitchWrites) first if the deletion was not intended."],"exampleFix":"// before\nvtctldclient WorkflowDelete --workflow sales commerce // error: writes already switched\n// after\nvtctldclient MoveTablesComplete --workflow sales --drop_source commerce","handlingStrategy":"validation","validationCode":"if writesSwitched(keyspace, workflow) {\n    return fmt.Errorf(\"use MoveTablesComplete --drop_source instead of delete for %s\", workflow)\n}","typeGuard":"func isDeleteAfterWritesErr(err error) bool {\n    return errors.Is(err, workflow.ErrWorkflowDeleteWritesSwitched)\n}","tryCatchPattern":"if err := client.WorkflowDelete(ctx, ks, wf, false); err != nil {\n    if errors.Is(err, workflow.ErrWorkflowDeleteWritesSwitched) {\n        return client.MoveTablesComplete(ctx, ks, wf) // intended cleanup path\n    }\n    return err\n}","preventionTips":["After switching writes, use MoveTablesComplete/DropSource for cleanup","Do not use plain WorkflowDelete on finished MoveTables migrations","Check traffic state before any delete operation"],"tags":["vitess","movetables","workflow-delete","cutover"],"backgroundTag":"workflow-traffic-not-switched","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}