{"record":{"id":"6fa9c4208ca56a04","repo":"pentaho/pentaho-kettle","slug":"infinite-loop-detected-key","errorCode":null,"errorMessage":"infinite loop detected: + key","messagePattern":"infinite loop detected: \\+ key","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"plugins/core/impl/src/main/java/org/pentaho/di/trans/steps/closure/ClosureGenerator.java","lineNumber":116,"sourceCode":"          Object[] outputRow = RowDataUtil.allocateRowData( data.outputRowMeta.size() );\n          outputRow[ 0 ] = parent;\n          outputRow[ 1 ] = current;\n          outputRow[ 2 ] = data.parents.get( parent );\n          putRow( data.outputRowMeta, outputRow );\n        }\n      }\n\n      setOutputDone();\n      return false;\n    }\n\n    return true;\n  }\n\n  private void recurseParents( Object key, long distance ) {\n    // catch infinite loop - change at will\n    if ( distance > 50 ) {\n      throw new RuntimeException( \"infinite loop detected:\" + key );\n    }\n    Object parent = data.map.get( key );\n\n    if ( parent == null || parent == data.topLevel || parent.equals( data.topLevel ) ) {\n      return;\n    } else {\n      data.parents.put( parent, distance );\n      recurseParents( parent, distance + 1 );\n      return;\n    }\n  }\n\n  public boolean init( StepMetaInterface smi, StepDataInterface sdi ) {\n    meta = (ClosureGeneratorMeta) smi;\n    data = (ClosureGeneratorData) sdi;\n\n    if ( super.init( smi, sdi ) ) {\n      data.reading = true;","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/plugins/core/impl/src/main/java/org/pentaho/di/trans/steps/closure/ClosureGenerator.java#L98-L134","documentation":"ClosureGenerator.recurseParents walks the parent map building the closure table. As a safeguard against cyclic parent-child data, it throws a RuntimeException when recursion depth exceeds 50. It means the hierarchy data contains a cycle (a row's ancestry loops back on itself) rather than forming a tree.","triggerScenarios":"Processing input rows where a record is its own ancestor — e.g. rows A->B, B->C, C->A — causing the recursive parent lookup to never terminate.","commonSituations":"Dirty hierarchical data (employee-manager cycles), self-referencing rows (A->A), importing hierarchies from systems without referential constraints.","solutions":["Find and fix the cycle in the source data (detect rows whose parent chain loops)","Filter or flag self-referencing rows before the step","Pre-validate the hierarchy in a prior step or with a SQL cycle check","If legitimately deeper than 50 levels, adjust the hardcoded depth guard in a patched build"],"exampleFix":"// before (bad data)\nrow: id=A, parent=B; id=B, parent=A\n// after\nrow: id=B, parent=null (break the cycle at the root)","handlingStrategy":"validation","validationCode":"-- SQL cycle pre-check before the step\nWITH RECURSIVE chain(id, parent, depth) AS (\n  SELECT id, parent_id, 0 FROM tree\n  UNION ALL\n  SELECT t.id, t.parent_id, c.depth+1 FROM tree t JOIN chain c ON t.parent_id = c.id\n  WHERE depth < 60\n)\nSELECT id FROM chain WHERE depth > 50;","typeGuard":"boolean isAcyclic(Map<Object,Object> parentMap) {\n  for (Object k : parentMap.keySet()) {\n    int d = 0; Object cur = k;\n    while (cur != null && d <= 50) { cur = parentMap.get(cur); d++; }\n    if (d > 50) return false;\n  }\n  return true;\n}","tryCatchPattern":"try {\n  step.processRow();\n} catch (RuntimeException e) {\n  if (e.getMessage().startsWith(\"infinite loop detected\")) {\n    // cycle in hierarchy data; identify the key from the message and fix source rows\n  }\n}","preventionTips":["Enforce referential constraints on hierarchy tables","Reject self-referencing rows in upstream steps","Log the offending key to locate the cycle"],"tags":["recursion","data-quality","cycle"],"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"}