pentaho/pentaho-kettle · error · KettleException

Directory cannot become a child to itself

Error message

Directory cannot become a child to itself

What it means

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.

Solutions

  1. Before moving, verify the new parent is not inside the source subtree (walk the parent chain of newParent and compare ObjectIds)
  2. Choose a target parent outside the moved directory's subtree
  3. In UIs, visually disable drop targets that are descendants of the dragged node

Example fix

// before
repo.renameRepositoryDirectory( dirAId, repo.findDirectory( "/a/b" ), null ); // /a/b is inside /a -> throws
// after
RepositoryDirectoryInterface newParent = repo.findDirectory( "/a/b" );
boolean inside = false;
for ( RepositoryDirectoryInterface p = newParent; p != null; p = p.getParent() ) {
  if ( p.getObjectId().equals( dirAId ) ) { inside = true; break; }
}
if ( !inside ) repo.renameRepositoryDirectory( dirAId, newParent, null );
Defensive patterns

Strategy: validation

Validate before calling

boolean isDescendant( RepositoryDirectoryInterface candidateChild, ObjectId ancestorId ) {
  for ( RepositoryDirectoryInterface p = candidateChild; p != null; p = p.getParent() ) {
    if ( p.getObjectId().equals( ancestorId ) ) return true;
  }
  return false;
}
// reject move if isDescendant( newParentDir, dirId )

Type guard

boolean canMoveInto( ObjectId dirId, RepositoryDirectoryInterface newParent ) {
  ObjectId p = newParent == null ? null : newParent.getObjectId();
  while ( p != null ) {
    if ( p.equals( dirId ) ) return false;
    p = parentOf( p );
  }
  return true;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13). Data as JSON: /api/errors/3449fab423ed50f1. Report an issue: GitHub.

Appendix: source

Thrown at engine/src/main/java/org/pentaho/di/repository/kdr/delegates/KettleDatabaseRepositoryDirectoryDelegate.java:221

   * @param id_directory
   *          Id of the directory to be moved/renamed
   * @param id_directory_parent
   *          Id of the new parent directory (null if the parent does not change)
   * @param newName
   *          New name for this directory (null if the name does not change)
   * @throws KettleException
   */
  public synchronized void renameDirectory( ObjectId id_directory, ObjectId id_directory_parent, String newName ) throws KettleException {
    if ( id_directory.equals( id_directory_parent ) ) {
      // Make sure the directory cannot become its own parent
      throw new KettleException( "Failed to copy directory into itself" );
    } else {
      // Make sure the directory does not become a descendant of itself
      RepositoryDirectory rd = new RepositoryDirectory();
      loadRepositoryDirectory( rd, id_directory );
      if ( rd.findDirectory( id_directory_parent ) != null ) {
        // The parent directory is a child of this directory. Do not proceed
        throw new KettleException( "Directory cannot become a child to itself" );
      } else {
        // Check for duplication
        RepositoryDirectory newParent = new RepositoryDirectory();
        loadRepositoryDirectory( newParent, id_directory_parent );
        RepositoryDirectory child = newParent.findChild( newName == null ? rd.getName() : newName );
        if ( child != null ) {
          throw new KettleException( "Destination directory already contains a diectory with requested name" );
        }
      }
    }

    if ( id_directory_parent != null || newName != null ) {
      RowMetaAndData r = new RowMetaAndData();

      String sql = "UPDATE " + quoteTable( KettleDatabaseRepository.TABLE_R_DIRECTORY ) + " SET ";
      boolean additionalParameter = false;

      if ( newName != null ) {

View on GitHub (pinned to f3058517a1)