{"record":{"id":"a782dbb0c92f69e4","repo":"apache/beam","slug":"last-attempted-key-was-s-in-range-s-claiming-work-in-s-s-was","errorCode":null,"errorMessage":"Last attempted key was %s in range %s, claiming work in [%s, %s) was not attempted","messagePattern":"Last attempted key was (.+?) in range (.+?), claiming work in \\[(.+?), (.+?)\\) was not attempted","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"sdks/java/core/src/main/java/org/apache/beam/sdk/transforms/splittabledofn/ByteKeyRangeTracker.java","lineNumber":199,"sourceCode":"        lastAttemptedKey != null,\n        \"Key range is non-empty %s and no keys have been attempted.\",\n        range);\n\n    // Return if the last attempted key was the empty key representing the end of range for\n    // all ranges.\n    if (lastAttemptedKey.isEmpty()) {\n      return;\n    }\n\n    // The lastAttemptedKey is the last key of current restriction.\n    if (!range.getEndKey().isEmpty() && next(lastAttemptedKey).compareTo(range.getEndKey()) >= 0) {\n      return;\n    }\n\n    // If the last attempted key was not at or beyond the end of the range then throw.\n    if (range.getEndKey().isEmpty() || range.getEndKey().compareTo(lastAttemptedKey) > 0) {\n      ByteKey nextKey = next(lastAttemptedKey);\n      throw new IllegalStateException(\n          String.format(\n              \"Last attempted key was %s in range %s, claiming work in [%s, %s) was not attempted\",\n              lastAttemptedKey, range, nextKey, range.getEndKey()));\n    }\n  }\n\n  @Override\n  public IsBounded isBounded() {\n    return IsBounded.BOUNDED;\n  }\n\n  @Override\n  public String toString() {\n    return MoreObjects.toStringHelper(this)\n        .add(\"range\", range)\n        .add(\"lastClaimedKey\", lastClaimedKey)\n        .add(\"lastAttemptedKey\", lastAttemptedKey)\n        .toString();","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/core/src/main/java/org/apache/beam/sdk/transforms/splittabledofn/ByteKeyRangeTracker.java#L181-L217","documentation":"ByteKeyRangeTracker.checkDone verifies that a splittable-DoFn claim covered the whole key range. If the last attempted key is still strictly inside the range (or the end key is empty/unbounded relative to it), Beam throws IllegalStateException saying work in [nextKey, endKey) was never claimed. It signals the tracker was marked done prematurely.","triggerScenarios":"Calling tryClaim/trySplit such that markDone or checkDone is invoked while lastAttemptedKey < range.getEndKey() (and endKey not empty); e.g. stopping iteration over ByteKeys before reaching the end and then calling markDone().","commonSituations":"Custom splittable DoFn over byte-key ranges that breaks out of the claim loop early; runner-driven checkpoint tests where the restriction was split but completion was asserted; bugs in restriction tracking logic treating empty ranges incorrectly.","solutions":["Continue claiming keys until the returned tryClaim result indicates the range is exhausted before calling markDone","Verify your loop processes ByteKey.next(lastAttemptedKey) .. endKey; the message names the unclaimed interval","Don't call checkDone/markDone when a split/checkpoint left part of the range unclaimed - that portion belongs to another split","Ensure the range's endKey handling matches the empty-end-key (unbounded) semantics in your tracker usage"],"exampleFix":"// before\nwhile (keys.hasNext()) {\n  if (!tracker.tryClaim(keys.next())) break;\n}\ntracker.markDone(); // may throw: range not fully claimed\n\n// after\nByteKey k = start;\nwhile (true) {\n  if (!tracker.tryClaim(k)) break; // false = range done\n  process(k);\n  k = ByteKey.next(k);\n}\ntracker.markDone(); // safe: all keys claimed or range exhausted","handlingStrategy":"try-catch","validationCode":"// Before markDone, confirm the range is exhausted\nif (tracker.currentRestriction().getEndKey().compareTo(lastClaimedKey) > 0\n    && !lastClaimedKey.isEmpty()) {\n  throw new IllegalStateException(\"Range not fully claimed: \"\n      + ByteKey.next(lastClaimedKey) + \" .. \"\n      + tracker.currentRestriction().getEndKey());\n}","typeGuard":null,"tryCatchPattern":"try {\n  tracker.markDone();\n} catch (IllegalStateException e) {\n  if (e.getMessage().contains(\"was not attempted\")) {\n    // continue claiming from the reported next key instead of failing the bundle\n  }\n}","preventionTips":["Always loop until tryClaim returns false before calling markDone","Never break out of claim loops early; leave unclaimed work via trySplit for redistribution","Write unit tests mirroring testTryClaim/testCheckpointUnstarted for custom key-range trackers"],"tags":["java","apache-beam","splittable-dofn","state-error","restriction-tracking"],"backgroundTag":"invalid-state-transition","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}