{"record":{"id":"e9d014eac3f1c1aa","repo":"pentaho/pentaho-kettle","slug":"purrepository-filecannotbesavedinrootdirectory","errorCode":"PurRepository.fileCannotBeSavedInRootDirectory","errorMessage":"PurRepository.fileCannotBeSavedInRootDirectory (localized, takes element name+extension; cannot be saved in root directory)","messagePattern":"PurRepository\\.fileCannotBeSavedInRootDirectory \\(localized, takes element name\\+extension; cannot be saved in root directory\\)","errorType":"exception","errorClass":"KettleException","httpStatus":null,"severity":"error","filePath":"plugins/pur/core/src/main/java/org/pentaho/di/repository/pur/PurRepository.java","lineNumber":3441,"sourceCode":"      case JOB:\n        objectTransformer = jobDelegate;\n        break;\n      default:\n        throw new KettleException(\n          \"Unknown RepositoryObjectType. Should be TRANSFORMATION or JOB \" );\n    }\n    saveTransOrJob( objectTransformer, element, versionComment, versionDate, saveSharedObjects, checkLock, checkRename,\n      loadRevision, checkDeleted );\n  }\n\n  protected void saveTransOrJob( ISharedObjectsTransformer objectTransformer, RepositoryElementInterface element,\n                                 String versionComment, Calendar versionDate,\n                                 boolean saveSharedObjects,\n                                 boolean checkLock, boolean checkRename,\n                                 boolean loadRevision, boolean checkDeleted ) throws KettleException {\n    if ( Import.ROOT_DIRECTORY.equals( element.getRepositoryDirectory().toString() ) ) {\n      // We don't have possibility to read this file via UI\n      throw new KettleException( BaseMessages.getString( PKG, \"PurRepository.fileCannotBeSavedInRootDirectory\",\n        element.getName() + element.getRepositoryElementType().getExtension() ) );\n    }\n\n    ExtensionPointHandler.callExtensionPoint( log, KettleExtensionPoint.BeforeSaveToRepository.id, element );\n\n    final boolean isUpdate = ( element.getObjectId() != null && element.getObjectId().getId() != null  );\n    RepositoryFile file = null;\n    if ( isUpdate ) {\n\n      readWriteLock.readLock().lock();\n      try {\n        ObjectId id = element.getObjectId();\n        file = pur.getFileById( id.getId() );\n\n        if ( checkLock && file.isLocked() && !unifiedRepositoryLockService.canUnlockFileById( id ) ) {\n          throw new KettleException( \"File is currently locked by another user for editing\" );\n        }\n        if ( checkDeleted && isInTrash( file ) ) {","sourceCodeStart":3423,"sourceCodeEnd":3459,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/plugins/pur/core/src/main/java/org/pentaho/di/repository/pur/PurRepository.java#L3423-L3459","documentation":"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.","triggerScenarios":"Calling PurRepository.save / saveRepositoryObject where element.getRepositoryDirectory().toString() equals Import.ROOT_DIRECTORY (\"/\"), i.e. the element has no subdirectory assigned.","commonSituations":"Programmatically generated transformations saved without calling setRepositoryDirectory; importing jobs/trans into '/' via scripts or the Java API; default/uninitialized RepositoryDirectory in freshly built metadata.","solutions":["Set a non-root directory on the element before saving: element.setRepositoryDirectory(new RepositoryDirectory(...)) or a real folder in the repository","Save via 'Save As' in Spoon into a subfolder instead of the root","Validate the target directory before invoking save and reject or relocate root-directory saves"],"exampleFix":"// before\ntransMeta.setRepositoryDirectory(rootDir); // \"/\"\nrepository.save(transMeta, \"v\", null, true);\n// after\nRepositoryDirectory sub = new RepositoryDirectory(rootDir, \"etl\");\ntransMeta.setRepositoryDirectory(sub);\nrepository.save(transMeta, \"v\", null, true);","handlingStrategy":"validation","validationCode":"if (Import.ROOT_DIRECTORY.equals(element.getRepositoryDirectory().toString())) {\n  throw new KettleException(\"Assign a subdirectory before saving: \" + element.getName());\n}","typeGuard":null,"tryCatchPattern":"try {\n  repository.save(element, comment, null, true);\n} catch (KettleException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"cannot be saved in root\")) {\n    element.setRepositoryDirectory(someSubdirectory);\n    repository.save(element, comment, null, true);\n  } else throw e;\n}","preventionTips":["Always call setRepositoryDirectory with a real folder before save","Never leave RepositoryDirectory at its default root in generated metadata","Validate target path in save helper utilities"],"tags":["kettle","pentaho","repository","root-directory","validation"],"backgroundTag":"invalid-argument-value","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"}