{"record":{"id":"3d45018603e4673c","repo":"apache/beam","slug":"claim-s-is-before-start-s","errorCode":null,"errorMessage":"Claim '%s' is before start '%s'","messagePattern":"Claim '(.+?)' is before start '(.+?)'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/io/range_trackers.py","lineNumber":251,"sourceCode":"    self._lock = threading.Lock()\n    self._last_claim = self.UNSTARTED\n\n  def start_position(self):\n    return self._start_position\n\n  def stop_position(self):\n    with self._lock:\n      return self._stop_position\n\n  def try_claim(self, position):\n    with self._lock:\n      if self._last_claim is not self.UNSTARTED and position < self._last_claim:\n        raise ValueError(\n            \"Positions must be claimed in order: \"\n            \"claim '%s' attempted after claim '%s'\" %\n            (position, self._last_claim))\n      elif self._start_position is not None and position < self._start_position:\n        raise ValueError(\n            \"Claim '%s' is before start '%s'\" %\n            (position, self._start_position))\n      if self._stop_position is None or position < self._stop_position:\n        self._last_claim = position\n        return True\n      else:\n        return False\n\n  def position_at_fraction(self, fraction):\n    return self.fraction_to_position(\n        fraction, self._start_position, self._stop_position)\n\n  def try_split(self, position):\n    with self._lock:\n      if ((self._stop_position is not None and position >= self._stop_position)\n          or (self._start_position is not None and\n              position <= self._start_position)):\n        _LOGGER.debug(","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/io/range_trackers.py#L233-L269","documentation":"The same try_claim() also validates the lower bound of the restriction: it raises ValueError when a claimed position is before the restriction's start position. This keeps claims inside the granted restriction.","triggerScenarios":"Calling try_claim(position) where self._start_position is not None and position < self._start_position, e.g. iterating a cursor initialized to an offset before the restriction start (common with default cursors of 0 when the restriction starts elsewhere).","commonSituations":"Custom RestrictionTracker whose process loop starts from element index 0 instead of restriction.start(); off-by-one in restriction construction where a restriction like (5,10) is iterated from 0.","solutions":["Initialize the claim cursor to the restriction's start position (self.current_restriction().start) before the process loop.","Fix restriction construction so start matches where iteration actually begins.","Clamp positions to the restriction start before calling try_claim."],"exampleFix":"// before\npos = 0\nwhile self.try_claim(pos):\n  process(pos); pos += 1\n// after\npos = self.current_restriction().start\nwhile self.try_claim(pos):\n  process(pos); pos += 1","handlingStrategy":"validation","validationCode":"start = tracker.current_restriction().start\nif position < start:\n  position = start  # clamp before claiming","typeGuard":null,"tryCatchPattern":"try:\n  tracker.try_claim(position)\nexcept ValueError as e:\n  logging.error('claim before restriction start: %s', e)","preventionTips":["Initialize cursors from restriction.start, not 0","Clamp positions to the restriction bounds before claiming","Add unit tests iterating restrictions with non-zero start"],"tags":["python","apache-beam","restriction-tracker"],"backgroundTag":"invalid-argument-value","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"}