{"record":{"id":"62d83223fcd09539","repo":"apache/druid","slug":"invalid-stagenumber-s","errorCode":null,"errorMessage":"Invalid stageNumber [%s]","messagePattern":"Invalid stageNumber \\[(.+?)\\]","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"multi-stage-query/src/main/java/org/apache/druid/msq/kernel/StageId.java","lineNumber":50,"sourceCode":"/**\n * Globally unique stage identifier: query ID plus stage number.\n *\n * Note: Versions till Druid 30 had a bug in the QueryKits which populated the {@link #queryId} field with random\n * UUIDs. Therefore, all usage of the field must be vetted instead of assuming that it will be the expected query id\n */\npublic class StageId implements Comparable<StageId>\n{\n  private static final Comparator<StageId> COMPARATOR =\n      Comparator.comparing(StageId::getQueryId)\n                .thenComparing(StageId::getStageNumber);\n\n  private final String queryId;\n  private final int stageNumber;\n\n  public StageId(final String queryId, final int stageNumber)\n  {\n    if (stageNumber < 0) {\n      throw new IAE(\"Invalid stageNumber [%s]\", stageNumber);\n    }\n\n    this.queryId = IdUtils.validateId(\"queryId\", queryId);\n    this.stageNumber = stageNumber;\n  }\n\n  @JsonCreator\n  public static StageId fromString(final String s)\n  {\n    final int lastUnderscore = s.lastIndexOf('_');\n\n    if (lastUnderscore > 0 && lastUnderscore < s.length() - 1) {\n      final Long stageNumber = GuavaUtils.tryParseLong(s.substring(lastUnderscore + 1));\n\n      if (stageNumber != null && stageNumber >= 0 && stageNumber <= Integer.MAX_VALUE) {\n        return new StageId(s.substring(0, lastUnderscore), stageNumber.intValue());\n      }\n    }","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/multi-stage-query/src/main/java/org/apache/druid/msq/kernel/StageId.java#L32-L68","documentation":"StageId's constructor validates that a stage number is non-negative, throwing IAE with 'Invalid stageNumber [%s]' when a negative value is passed. Stage numbers in MSQ queries are sequential indices starting at 0, so a negative stage number is always a caller bug. This fails fast before the StageId is used to key kernels or work orders.","triggerScenarios":"Calling new StageId(queryId, stageNumber) with a negative int, e.g. computing a stage index from an uninitialized counter, an off-by-one subtraction, or deserializing a payload with stageNumber < 0.","commonSituations":"Custom controllers or tooling that computes stage numbers arithmetically; plugins that offset stage indices (stageNumber - 1) before the first stage; JSON/config ingestion where the stage field defaulted to -1.","solutions":["Inspect the caller and fix the arithmetic or initialization so stageNumber is >= 0","Validate stageNumber before constructing StageId, or clamp/reject negative values at the input boundary","Log the full queryId and stage counter around the call to find where the negative value originates"],"exampleFix":"// before\nStageId id = new StageId(queryId, stageNumber - 1);\n// after\nif (stageNumber - 1 < 0) { throw new IllegalArgumentException(\"Cannot decrement stage 0\"); }\nStageId id = new StageId(queryId, stageNumber - 1);","handlingStrategy":"validation","validationCode":"if (stageNumber < 0) throw new IllegalArgumentException(\"stageNumber must be >= 0, got \" + stageNumber);\nStageId id = new StageId(queryId, stageNumber);","typeGuard":"boolean isValidStageNumber(int n) { return n >= 0 && n <= Integer.MAX_VALUE; }","tryCatchPattern":null,"preventionTips":["Never compute stage numbers by subtraction without bounds checks","Initialize stage counters to 0, not -1","Validate stage numbers at deserialization boundaries"],"tags":["java","argument-validation","msq"],"backgroundTag":"invalid-argument-value","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}