pentaho/pentaho-kettle · error · KettleXMLException

Unable to load job entry of type 'shell' from XML node

Error message

Unable to load job entry of type 'shell' from XML node

What it means

JobEntryShell.loadXML parses the job entry's XML node into fields; any KettleException raised while reading tags is wrapped in KettleXMLException('Unable to load job entry of type 'shell' from XML node'). The message identifies which job entry type failed to deserialize, with the parse error as cause.

Solutions

  1. Open the .kjb in an XML editor and validate it is well-formed
  2. Compare the shell job entry node against a working one (check argument0..argumentN tags and argnr)
  3. Recreate the shell job entry in Spoon and re-save the job file
  4. Restore the file from version control/backup if it was truncated
Defensive patterns

Strategy: try-catch

Try / catch

try {
  jobEntryShell.loadXML(entrynode, databases, metaStore);
} catch (KettleXMLException e) {
  logError("Shell job entry XML invalid: " + e.getCause().getMessage(), e);
  // fall back to re-creating the entry or loading a backup .kjb
}

Prevention

When it happens

Trigger: XMLHandler.getTagValue calls throw (malformed XML document, null entrynode) while loading a 'shell' job entry from a .kjb file, including reading the repeated argument0..argumentN tags.

Common situations: Hand-edited or truncated .kjb files; XML saved by a newer/older Pentaho version with divergent tag layout; copy-paste of job entries losing attributes; invalid characters in arguments breaking XML escaping.

Related errors


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

Appendix: source

Thrown at engine/src/main/java/org/pentaho/di/job/entries/shell/JobEntryShell.java:185

      insertScript = "Y".equalsIgnoreCase( XMLHandler.getTagValue( entrynode, "insertScript" ) );

      script = XMLHandler.getTagValue( entrynode, "script" );

      // How many arguments?
      int argnr = 0;
      while ( XMLHandler.getTagValue( entrynode, "argument" + argnr ) != null ) {
        argnr++;
      }
      allocate( argnr );

      // Read them all...
      // THIS IS A VERY BAD WAY OF READING/SAVING AS IT MAKES
      // THE XML "DUBIOUS". DON'T REUSE IT.
      for ( int a = 0; a < argnr; a++ ) {
        arguments[a] = XMLHandler.getTagValue( entrynode, "argument" + a );
      }
    } catch ( KettleException e ) {
      throw new KettleXMLException( "Unable to load job entry of type 'shell' from XML node", e );
    }
  }

  // Load the jobentry from repository
  public void loadRep( Repository rep, IMetaStore metaStore, ObjectId id_jobentry, List<DatabaseMeta> databases,
    List<SlaveServer> slaveServers ) throws KettleException {
    try {
      setFileName( rep.getJobEntryAttributeString( id_jobentry, "file_name" ) );
      setWorkDirectory( rep.getJobEntryAttributeString( id_jobentry, "work_directory" ) );
      argFromPrevious = rep.getJobEntryAttributeBoolean( id_jobentry, "arg_from_previous" );
      execPerRow = rep.getJobEntryAttributeBoolean( id_jobentry, "exec_per_row" );

      setLogfile = rep.getJobEntryAttributeBoolean( id_jobentry, "set_logfile" );
      setAppendLogfile = rep.getJobEntryAttributeBoolean( id_jobentry, "set_append_logfile" );
      addDate = rep.getJobEntryAttributeBoolean( id_jobentry, "add_date" );
      addTime = rep.getJobEntryAttributeBoolean( id_jobentry, "add_time" );
      logfile = rep.getJobEntryAttributeString( id_jobentry, "logfile" );
      logext = rep.getJobEntryAttributeString( id_jobentry, "logext" );

View on GitHub (pinned to f3058517a1)