{"record":{"id":"b7c48073ac2f96c6","repo":"pentaho/pentaho-kettle","slug":"the-number-of-hops-read","errorCode":null,"errorMessage":"The number of hops read [","messagePattern":"The number of hops read \\[","errorType":"exception","errorClass":"KettleException","httpStatus":null,"severity":"error","filePath":"plugins/pur/core/src/main/java/org/pentaho/di/repository/pur/JobDelegate.java","lineNumber":293,"sourceCode":"      }\n\n      boolean unconditional = true;\n      if ( hopNode.hasProperty( JOB_HOP_UNCONDITIONAL ) ) {\n        unconditional = hopNode.getProperty( JOB_HOP_UNCONDITIONAL ).getBoolean();\n      }\n\n      JobEntryCopy copyFrom = jobMeta.findJobEntry( copyFromName, copyFromNr, true );\n      JobEntryCopy copyTo = jobMeta.findJobEntry( copyToName, copyToNr, true );\n\n      JobHopMeta jobHopMeta = new JobHopMeta( copyFrom, copyTo );\n      jobHopMeta.setEnabled( enabled );\n      jobHopMeta.setEvaluation( evaluation );\n      jobHopMeta.setUnconditional( unconditional );\n      jobMeta.addJobHop( jobHopMeta );\n\n    }\n    if ( jobMeta.nrJobHops() != nrHops ) {\n      throw new KettleException( \"The number of hops read [\" + jobMeta.nrJobHops()\n          + \"] was not the number we expected [\" + nrHops + \"]\" );\n    }\n\n    // Load the details at the end, to make sure we reference the databases correctly, etc.\n    //\n    loadJobMetaDetails( rootNode, jobMeta );\n\n    jobMeta.eraseParameters();\n    DataNode paramsNode = rootNode.getNode( NODE_PARAMETERS );\n    int count = (int) paramsNode.getProperty( PROP_NR_PARAMETERS ).getLong();\n    for ( int idx = 0; idx < count; idx++ ) {\n      DataNode paramNode = paramsNode.getNode( PARAM_PREFIX + idx );\n      String key = getString( paramNode, PARAM_KEY );\n      String def = getString( paramNode, PARAM_DEFAULT );\n      String desc = getString( paramNode, PARAM_DESC );\n      jobMeta.addParameterDefinition( key, def, desc );\n    }\n  }","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/plugins/pur/core/src/main/java/org/pentaho/di/repository/pur/JobDelegate.java#L275-L311","documentation":"After reading job hops (JobHopMeta) from the repository DataNode and adding them to jobMeta, JobDelegate asserts jobMeta.nrJobHops() equals the expected nrHops. A mismatch means stored hop entries disagree with the recorded hop count, so the job is considered corrupt and loading fails, preventing broken job graphs (orphaned hops, wrong evaluation conditions) from being used.","triggerScenarios":"Loading a job whose DataNode's NODE_HOPS children count differs from the persisted nrHops — caused by partial/interrupted saves, concurrent modifications, or version drift in how hops were serialized into the repository.","commonSituations":"Repository corruption after aborted saves; two users editing the same job concurrently; importing jobs exported from a different PDI/Pentaho version; direct manipulation of repository storage.","solutions":["Restore the job from a backup or re-save it in PDI so hops and counts are rewritten consistently.","Diff the stored NODE_HOPS children against nrHops to identify dropped hops, then fix the stored data.","Match PDI client and Pentaho server versions to rule out serialization drift.","Delete the job from the repository and re-import from a known-good .kjb export.","Catch KettleException in automation and fall back to a cached/backup copy of the job metadata."],"exampleFix":"// before\nJobMeta jobMeta = (JobMeta) repository.loadJob(jobId, null).loadAll(); // throws on hop-count mismatch\n// after\ntry {\n  jobMeta = (JobMeta) repository.loadJob(jobId, null).loadAll();\n} catch (KettleException e) {\n  if (e.getMessage().startsWith(\"The number of hops read\")) {\n    jobMeta = importJobFromFile(backupKjbPath);\n  } else {\n    throw e;\n  }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  JobMeta jobMeta = (JobMeta) repository.loadJob(jobId, null).loadAll();\n} catch (KettleException e) {\n  if (e.getMessage().startsWith(\"The number of hops read\")) {\n    // hop list inconsistent -> re-import or restore job\n  } else {\n    throw e;\n  }\n}","preventionTips":["Prevent concurrent modifications of the same job in the repository.","Never edit repository content stores directly.","Keep PDI client and Pentaho server versions synchronized.","Regularly export jobs to .kjb files as a recovery path."],"tags":["pentaho","kettle","repository","corruption","deserialization"],"backgroundTag":"internal-invariant-violation","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"}