stanfordnlp/CoreNLP · error · SsurgeonParseException

-headIndex of is out of bounds for a phrase with words

Error message

-headIndex of ${headIndex} is out of bounds for a phrase with ${nodes.size()} words

What it means

SetPhraseHead validates that headIndex is a 0-indexed position within the supplied node list; values below 0 or at/after nodes.size() point at no node in the phrase and the constructor throws with the actual index and list size in the message.

Solutions

  1. Set -headIndex to a value between 0 and (number of -node arguments - 1)
  2. Count the -node entries in the rule and adjust headIndex; remember it is 0-indexed
  3. Update headIndex whenever you add or remove nodes from the phrase

Example fix

// before
new SetPhraseHead(Arrays.asList("n1","n2"), 2, reln, 1.0); // out of bounds
// after
new SetPhraseHead(Arrays.asList("n1","n2"), 1, reln, 1.0);
Defensive patterns

Strategy: validation

Validate before calling

if (headIndex != null && (headIndex < 0 || headIndex >= nodes.size())) {
  throw new IllegalArgumentException("headIndex " + headIndex + " must be in [0, " + (nodes.size()-1) + "]");
}

Type guard

boolean headIndexInRange(Integer idx, int numNodes) { return idx != null && idx >= 0 && idx < numNodes; }

Try / catch

try {
  op = new SetPhraseHead(nodes, headIndex, relation, weight);
} catch (SsurgeonParseException e) {
  log.error("-headIndex out of bounds: {}", e.getMessage());
  throw new RuleSyntaxException(e);
}

Prevention

When it happens

Trigger: new SetPhraseHead(nodes, headIndex, ...) where headIndex < 0 or headIndex >= nodes.size(), e.g. headIndex 2 with a 2-node list, or an Ssurgeon line with -headIndex 1 when only one -node was given.

Common situations: Confusing 1-based and 0-based indexing in rules; adding/removing -node arguments from the rule without updating -headIndex; generating rules programmatically where the node list shrunk.

Related errors


AI-assisted analysis of stanfordnlp/CoreNLP@1b7edd19c4 (2026-09-10). Data as JSON: /api/errors/52f03220afe3bde2. Report an issue: GitHub.

Appendix: source

Thrown at src/edu/stanford/nlp/semgraph/semgrex/ssurgeon/SetPhraseHead.java:35

 * If that condition is matched, then existing internal edges are replaced with edges to the new head, with the given reln <br>
 * If the head is changed, the edge out of the phrase (if it is not root) is changed to come from the new head <br>
 * Edges in to the phrase are also changed to point to the new head.
 * The purpose of that change is so for a noun phrase, for example, modifiers of that noun phrase such as nmod or nmod:desc now modify the new head
 */
public class SetPhraseHead extends SsurgeonEdit {
  public static final String LABEL = "setPhraseHead";

  final List<String> phrase;
  final int headIndex;
  final GrammaticalRelation relation;
  final double weight;

  public SetPhraseHead(List<String> nodes, Integer headIndex, GrammaticalRelation relation, double weight) {
    if (headIndex == null) {
      throw new SsurgeonParseException("SetPhraseHead expected a -headIndex, 0-indexed for the node to use as the new head");
    }
    if (headIndex < 0 || headIndex >= nodes.size()) {
      throw new SsurgeonParseException("-headIndex of " + headIndex + " is out of bounds for a phrase with " + nodes.size() + " words");
    }

    if (relation == null) {
      throw new SsurgeonParseException("SetPhraseHead expected a -reln to represent the dependency to use for the new phrase");
    }

    this.phrase = new ArrayList<>(nodes);
    this.headIndex = headIndex;
    this.relation = relation;
    this.weight = weight;
  }

  @Override
  public String toEditString() {
    StringWriter buf = new StringWriter();
    buf.write(LABEL);
    buf.write("\t");
    for (String node : phrase) {

View on GitHub (pinned to 1b7edd19c4)