apache/beam · error · ValueError

OffsetRestrictionTracker is not done since work in range [%s

Error message

OffsetRestrictionTracker is not done since work in range [%s, %s) has not been claimed.

What it means

OffsetRestrictionTracker.check_done() verifies that a splittable-DoFn restriction has been fully claimed before the element can be considered processed. Beam throws this ValueError when the range is non-empty but the last claim attempt never reached stop-1, meaning unclaimed work remains in [start, stop).

Source

Thrown at sdks/python/apache_beam/io/restriction_trackers.py:89


class OffsetRestrictionTracker(RestrictionTracker):
  """An `iobase.RestrictionTracker` implementations for an offset range.

  Offset range is represented as OffsetRange.
  """
  def __init__(self, offset_range: OffsetRange) -> None:
    assert isinstance(offset_range, OffsetRange), offset_range
    self._range = offset_range
    self._current_position = None
    self._last_claim_attempt = None
    self._checkpointed = False

  def check_done(self):
    if (self._range.start != self._range.stop and
        (self._last_claim_attempt is None or
         self._last_claim_attempt < self._range.stop - 1)):
      raise ValueError(
          'OffsetRestrictionTracker is not done since work in range [%s, %s) '
          'has not been claimed.' % (
              self._last_claim_attempt
              if self._last_claim_attempt is not None else self._range.start,
              self._range.stop))

  def current_restriction(self):
    return self._range

  def current_progress(self) -> RestrictionProgress:
    if self._current_position is None:
      fraction = 0.0
    elif self._range.stop == self._range.start:
      # If self._current_position is not None, we must be done.
      fraction = 1.0
    else:
      fraction = (
          float(self._current_position - self._range.start) /

View on GitHub (pinned to 12126d8942)

Solutions

  1. Ensure the processing loop claims every position up to stop-1, e.g. while tracker.try_claim(position)
  2. Remove early breaks/exits that skip claims, or checkpoint instead of exiting
  3. If the range is genuinely unprocessed, return a deferred residual rather than declaring done
  4. Verify the restriction passed to the tracker matches the offsets actually iterated

Example fix

// before
while position < restriction.stop:
  if not tracker.try_claim(position):
    break
  if some_condition:
    break  # leaves offsets unclaimed
  position += 1
// after
while tracker.try_claim(position):
  process(position)
  if some_condition:
    return a_deferred_residual(position)  # let Beam resume later
  position += 1
Defensive patterns

Strategy: validation

Validate before calling

def restriction_is_done(tracker):
    rng = tracker.current_restriction()
    return rng.start == rng.stop or tracker.last_claim_reached_stop()

Try / catch

try:
    tracker.check_done()
except ValueError as e:
    raise RuntimeError('SDF left unclaimed work; check process loop') from e

Prevention

When it happens

Trigger: Finishing a DoFn process_element without claiming every position in the range — e.g. claiming only positions 0..4 for OffsetRange(0, 10), or never calling try_claim at all before check_done.

Common situations: Custom splittable DoFns with early loop exits (break on a condition), skipped offsets inside a while loop, or forgetting to claim the final offset.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/9b8049735d0d5e5b. Report an issue: GitHub.