{"record":{"id":"9f4f62d0aadd28fe","repo":"vitessio/vitess","slug":"delete-v-v-failed-it-s-a-directory","errorCode":null,"errorMessage":"delete(%v, %v) failed: it's a directory","messagePattern":"delete\\((.+?), (.+?)\\) failed: it's a directory","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/topo/memorytopo/file.go","lineNumber":289,"sourceCode":"\t\treturn err\n\t}\n\n\t// Get the parent dir.\n\tdir, file := path.Split(filePath)\n\tp := c.factory.nodeByPath(c.cell, dir)\n\tif p == nil {\n\t\treturn topo.NewError(topo.NoNode, filePath)\n\t}\n\n\t// Get the existing file.\n\tn, ok := p.children[file]\n\tif !ok {\n\t\treturn topo.NewError(topo.NoNode, filePath)\n\t}\n\n\t// Check if it's a directory.\n\tif n.isDirectory() {\n\t\treturn fmt.Errorf(\"delete(%v, %v) failed: it's a directory\", c.cell, filePath)\n\t}\n\n\t// Check the version.\n\tif version != nil && n.version != uint64(version.(NodeVersion)) {\n\t\treturn topo.NewError(topo.BadVersion, filePath)\n\t}\n\n\t// Now we can delete.\n\tc.factory.recursiveDelete(n)\n\n\t// Call the watches\n\tfor _, w := range n.watches {\n\t\tif w.contents != nil {\n\t\t\tw.contents <- &topo.WatchData{\n\t\t\t\tErr: topo.NewError(topo.NoNode, filePath),\n\t\t\t}\n\t\t\tclose(w.contents)\n\t\t}","sourceCodeStart":271,"sourceCodeEnd":307,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/topo/memorytopo/file.go#L271-L307","documentation":"memorytopo's Delete refuses to delete a node that is itself a directory (has children). File/path-based memorytopo implementations treat container nodes as directories, and only leaf nodes can be deleted with Delete.","triggerScenarios":"Calling topo.Server.Delete (backed by memorytopo/file.go) on a path that contains child nodes, e.g. deleting a keyspace or shard directory path instead of a leaf value node.","commonSituations":"Application bug passing a parent path (keyspace/shard prefix) where a leaf path (individual node) was intended; leftover child nodes (e.g. replication records) under the path being cleaned up; using the file-based memorytopo for paths the regular memorytopo allowed.","solutions":["Verify the filePath passed to Delete points to a leaf node, not a directory","Delete the child nodes first, then the parent (or use a recursive delete helper)","Check for leftover children (e.g. shard replication nodes) under the target path","Use topo commands that handle hierarchy (e.g. DeleteKeyspace/DeleteShard) instead of raw Delete"],"exampleFix":"// before\nerr := ts.Delete(ctx, cell, keyspacePath, nil)\n// after\nchildren, _ := ts.List(ctx, cell, keyspacePath)\nfor _, child := range children {\n    ts.Delete(ctx, cell, child, nil)\n}\nerr := ts.Delete(ctx, cell, keyspacePath, nil)","handlingStrategy":"type-guard","validationCode":"_, err := ts.Get(ctx, cell, filePath)\nif err != nil && topo.IsErrType(err, topo.NoNode) {\n    return fmt.Errorf(\"path %s does not exist\", filePath)\n}","typeGuard":"func isDirectoryErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"it's a directory\")\n}","tryCatchPattern":"err := ts.Delete(ctx, cell, filePath, nil)\nif err != nil && strings.Contains(err.Error(), \"it's a directory\") {\n    // recursively delete children first, then retry\n}","preventionTips":["Only call Delete on leaf value nodes; use hierarchical delete helpers for prefixes","List children before deleting a path to confirm it is a leaf","Prefer vtctldclient keyspace/shard delete commands over raw topo Delete"],"tags":["topo","memorytopo","directory","delete"],"backgroundTag":"topo-delete-directory-not-allowed","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}