pentaho/pentaho-kettle · error · KettleException

PurRepository.fileCannotBeSavedInRootDirectory

PurRepository.fileCannotBeSavedInRootDirectory

Error message

PurRepository.fileCannotBeSavedInRootDirectory (localized, takes element name+extension; cannot be saved in root directory)

What it means

The PUR repository forbids saving transformation/job files directly in the repository root directory ('/'), because the UI has no way to read them back. The localized message includes the element name plus extension. This is an explicit pre-save path validation enforced before any network call.

Solutions

  1. Set a non-root directory on the element before saving: element.setRepositoryDirectory(new RepositoryDirectory(...)) or a real folder in the repository
  2. Save via 'Save As' in Spoon into a subfolder instead of the root
  3. Validate the target directory before invoking save and reject or relocate root-directory saves

Example fix

// before
transMeta.setRepositoryDirectory(rootDir); // "/"
repository.save(transMeta, "v", null, true);
// after
RepositoryDirectory sub = new RepositoryDirectory(rootDir, "etl");
transMeta.setRepositoryDirectory(sub);
repository.save(transMeta, "v", null, true);
Defensive patterns

Strategy: validation

Validate before calling

if (Import.ROOT_DIRECTORY.equals(element.getRepositoryDirectory().toString())) {
  throw new KettleException("Assign a subdirectory before saving: " + element.getName());
}

Try / catch

try {
  repository.save(element, comment, null, true);
} catch (KettleException e) {
  if (e.getMessage() != null && e.getMessage().contains("cannot be saved in root")) {
    element.setRepositoryDirectory(someSubdirectory);
    repository.save(element, comment, null, true);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling PurRepository.save / saveRepositoryObject where element.getRepositoryDirectory().toString() equals Import.ROOT_DIRECTORY ("/"), i.e. the element has no subdirectory assigned.

Common situations: Programmatically generated transformations saved without calling setRepositoryDirectory; importing jobs/trans into '/' via scripts or the Java API; default/uninitialized RepositoryDirectory in freshly built metadata.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at plugins/pur/core/src/main/java/org/pentaho/di/repository/pur/PurRepository.java:3441

      case JOB:
        objectTransformer = jobDelegate;
        break;
      default:
        throw new KettleException(
          "Unknown RepositoryObjectType. Should be TRANSFORMATION or JOB " );
    }
    saveTransOrJob( objectTransformer, element, versionComment, versionDate, saveSharedObjects, checkLock, checkRename,
      loadRevision, checkDeleted );
  }

  protected void saveTransOrJob( ISharedObjectsTransformer objectTransformer, RepositoryElementInterface element,
                                 String versionComment, Calendar versionDate,
                                 boolean saveSharedObjects,
                                 boolean checkLock, boolean checkRename,
                                 boolean loadRevision, boolean checkDeleted ) throws KettleException {
    if ( Import.ROOT_DIRECTORY.equals( element.getRepositoryDirectory().toString() ) ) {
      // We don't have possibility to read this file via UI
      throw new KettleException( BaseMessages.getString( PKG, "PurRepository.fileCannotBeSavedInRootDirectory",
        element.getName() + element.getRepositoryElementType().getExtension() ) );
    }

    ExtensionPointHandler.callExtensionPoint( log, KettleExtensionPoint.BeforeSaveToRepository.id, element );

    final boolean isUpdate = ( element.getObjectId() != null && element.getObjectId().getId() != null  );
    RepositoryFile file = null;
    if ( isUpdate ) {

      readWriteLock.readLock().lock();
      try {
        ObjectId id = element.getObjectId();
        file = pur.getFileById( id.getId() );

        if ( checkLock && file.isLocked() && !unifiedRepositoryLockService.canUnlockFileById( id ) ) {
          throw new KettleException( "File is currently locked by another user for editing" );
        }
        if ( checkDeleted && isInTrash( file ) ) {

View on GitHub (pinned to f3058517a1)