{"record":{"id":"3449fab423ed50f1","repo":"pentaho/pentaho-kettle","slug":"directory-cannot-become-a-child-to-itself","errorCode":null,"errorMessage":"Directory cannot become a child to itself","messagePattern":"Directory cannot become a child to itself","errorType":"exception","errorClass":"KettleException","httpStatus":null,"severity":"error","filePath":"engine/src/main/java/org/pentaho/di/repository/kdr/delegates/KettleDatabaseRepositoryDirectoryDelegate.java","lineNumber":221,"sourceCode":"   * @param id_directory\n   *          Id of the directory to be moved/renamed\n   * @param id_directory_parent\n   *          Id of the new parent directory (null if the parent does not change)\n   * @param newName\n   *          New name for this directory (null if the name does not change)\n   * @throws KettleException\n   */\n  public synchronized void renameDirectory( ObjectId id_directory, ObjectId id_directory_parent, String newName ) throws KettleException {\n    if ( id_directory.equals( id_directory_parent ) ) {\n      // Make sure the directory cannot become its own parent\n      throw new KettleException( \"Failed to copy directory into itself\" );\n    } else {\n      // Make sure the directory does not become a descendant of itself\n      RepositoryDirectory rd = new RepositoryDirectory();\n      loadRepositoryDirectory( rd, id_directory );\n      if ( rd.findDirectory( id_directory_parent ) != null ) {\n        // The parent directory is a child of this directory. Do not proceed\n        throw new KettleException( \"Directory cannot become a child to itself\" );\n      } else {\n        // Check for duplication\n        RepositoryDirectory newParent = new RepositoryDirectory();\n        loadRepositoryDirectory( newParent, id_directory_parent );\n        RepositoryDirectory child = newParent.findChild( newName == null ? rd.getName() : newName );\n        if ( child != null ) {\n          throw new KettleException( \"Destination directory already contains a diectory with requested name\" );\n        }\n      }\n    }\n\n    if ( id_directory_parent != null || newName != null ) {\n      RowMetaAndData r = new RowMetaAndData();\n\n      String sql = \"UPDATE \" + quoteTable( KettleDatabaseRepository.TABLE_R_DIRECTORY ) + \" SET \";\n      boolean additionalParameter = false;\n\n      if ( newName != null ) {","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/engine/src/main/java/org/pentaho/di/repository/kdr/delegates/KettleDatabaseRepositoryDirectoryDelegate.java#L203-L239","documentation":"Thrown by renameDirectory when the target parent directory is a descendant of the directory being moved, which would create a cycle in the directory tree. The method loads the subtree of id_directory and checks findDirectory(id_directory_parent); a non-null result means the parent lives inside the directory being moved. Thrown before any database mutation.","triggerScenarios":"repository.renameRepositoryDirectory(id, newParent, name) where newParent is a subdirectory (at any depth) of the directory identified by id, e.g. moving /a into /a/b.","commonSituations":"Recursive or scripted reorganization of transformation/job folders that accidentally moves a parent into its own child; drag-and-drop in Spoon dropping a folder onto one of its descendants.","solutions":["Before moving, verify the new parent is not inside the source subtree (walk the parent chain of newParent and compare ObjectIds)","Choose a target parent outside the moved directory's subtree","In UIs, visually disable drop targets that are descendants of the dragged node"],"exampleFix":"// before\nrepo.renameRepositoryDirectory( dirAId, repo.findDirectory( \"/a/b\" ), null ); // /a/b is inside /a -> throws\n// after\nRepositoryDirectoryInterface newParent = repo.findDirectory( \"/a/b\" );\nboolean inside = false;\nfor ( RepositoryDirectoryInterface p = newParent; p != null; p = p.getParent() ) {\n  if ( p.getObjectId().equals( dirAId ) ) { inside = true; break; }\n}\nif ( !inside ) repo.renameRepositoryDirectory( dirAId, newParent, null );","handlingStrategy":"validation","validationCode":"boolean isDescendant( RepositoryDirectoryInterface candidateChild, ObjectId ancestorId ) {\n  for ( RepositoryDirectoryInterface p = candidateChild; p != null; p = p.getParent() ) {\n    if ( p.getObjectId().equals( ancestorId ) ) return true;\n  }\n  return false;\n}\n// reject move if isDescendant( newParentDir, dirId )","typeGuard":"boolean canMoveInto( ObjectId dirId, RepositoryDirectoryInterface newParent ) {\n  ObjectId p = newParent == null ? null : newParent.getObjectId();\n  while ( p != null ) {\n    if ( p.equals( dirId ) ) return false;\n    p = parentOf( p );\n  }\n  return true;\n}","tryCatchPattern":null,"preventionTips":["Walk the new parent's ancestor chain and reject if it contains the moved directory","In tree UIs, disable drop targets that are descendants of the dragged node","Validate moves in scripted reorganizations before executing them"],"tags":["validation","directory-move","cycle-detection"],"backgroundTag":"invalid-state-transition","analyzedSha":"f3058517a153da500bf4551f46d79b91bf8ec552","analyzedAt":"2026-09-13T14:04:16.340Z","contentChangedAt":"2026-09-13T14:04:16.340Z","schemaVersion":2},"datasetVersion":"2026-09-20T23:17:15.980Z"}