{"record":{"id":"ca20fc6387756459","repo":"stanfordnlp/CoreNLP","slug":"attempted-to-replace-a-root-node-with-more-than-on","errorCode":null,"errorMessage":"Attempted to replace a root node with more than one node, unable to proceed","messagePattern":"Attempted to replace a root node with more than one node, unable to proceed","errorType":"exception","errorClass":"TsurgeonRuntimeException","httpStatus":null,"severity":"error","filePath":"src/edu/stanford/nlp/trees/tregex/tsurgeon/ReplaceNode.java","lineNumber":41,"sourceCode":"\n  private static final Function<AuxiliaryTree, HoldTreeNode> convertAuxiliaryToHold = t -> new HoldTreeNode(t);\n\n  @Override\n  public TsurgeonMatcher matcher(Map<String,Tree> newNodeNames, CoindexationGenerator coindexer) {\n    return new Matcher(newNodeNames, coindexer);\n  }\n\n  private class Matcher extends TsurgeonMatcher {\n    public Matcher(Map<String,Tree> newNodeNames, CoindexationGenerator coindexer) {\n      super(ReplaceNode.this, newNodeNames, coindexer);\n    }\n\n    @Override\n    public Tree evaluate(Tree tree, TregexMatcher tregex) {\n      Tree oldNode = childMatcher[0].evaluate(tree, tregex);\n      if (oldNode==tree) {\n        if (children.length > 2) {\n          throw new TsurgeonRuntimeException(\"Attempted to replace a root node with more than one node, unable to proceed\");\n        }\n        return childMatcher[1].evaluate(tree, tregex);\n      }\n      Tree parent = oldNode.parent(tree);\n      int i = parent.objectIndexOf(oldNode);\n      parent.removeChild(i);\n      for (int j = 1; j < children.length; ++j) {\n        Tree newNode = childMatcher[j].evaluate(tree, tregex);\n        parent.insertDtr(newNode.deepCopy(), i + j - 1);\n      }\n      return tree;\n    }\n  }\n}\n","sourceCodeStart":23,"sourceCodeEnd":56,"githubUrl":"https://github.com/stanfordnlp/CoreNLP/blob/1b7edd19c4d0d7b1f13a2591425b9b60a0b1af7a/src/edu/stanford/nlp/trees/tregex/tsurgeon/ReplaceNode.java#L23-L56","documentation":"ReplaceNode.evaluate replaces the matched node with new children. When the matched node is the root of the whole tree, the replacement must yield exactly one root; if more than one replacement node was supplied, the operation cannot attach multiple roots and throws TsurgeonRuntimeException.","triggerScenarios":"A replace Tsurgeon operation (replace <match> <node1> <node2> ...) where the tregex match binds the root node (oldNode == tree) and the replacement list contains more than two entries (children.length > 2, i.e. more than one replacement node).","commonSituations":"Writing 'replace =root X Y' style Tsurgeon scripts; patterns that unintentionally match the root (e.g. bare tree-level labels like \"ROOT\" or \"@S\" matching the top node); reusing a multi-node replacement script against patterns that also hit roots.","solutions":["Make the tregex pattern match only non-root nodes (e.g. require a parent: \"@NP < __\" or exclude ROOT).","Supply exactly one replacement node if the pattern may match the root.","Catch TsurgeonRuntimeException and skip/adjust the match when replacing roots with multiple nodes is intended to be disallowed."],"exampleFix":"// before\ntregex: \"@ROOT\" ; replace: =root /S/ /NP/  // multi-node replace on root\n// after\ntregex: \"@ROOT <1=child\" ; replace: =child /S/  // replace a non-root child, single node","handlingStrategy":"try-catch","validationCode":"// only allow multi-node replacements for non-root matches\nboolean safeReplace(String tregex, int replacementCount) {\n  boolean matchesRoot = tregex.contains(\"ROOT\") || !tregex.contains(\"<\");\n  return !(matchesRoot && replacementCount > 1);\n}","typeGuard":null,"tryCatchPattern":"try {\n  tree = op.evaluate(tree, matcher);\n} catch (TsurgeonRuntimeException e) {\n  if (e.getMessage().contains(\"replace a root node\")) {\n    log.warn(\"Skipping root replacement: \" + e.getMessage());\n  } else throw e;\n}","preventionTips":["Write tregex patterns that exclude the root when using multi-node replace.","Limit replacement lists to a single node unless the match is guaranteed non-root.","Run scripts over a sample corpus first to detect accidental root matches."],"tags":["tsurgeon","tree-manipulation","invalid-argument"],"backgroundTag":"invalid-argument-value","analyzedSha":"1b7edd19c4d0d7b1f13a2591425b9b60a0b1af7a","analyzedAt":"2026-09-10T02:24:07.274Z","contentChangedAt":"2026-09-10T02:24:07.274Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}