{"record":{"id":"2d064046416eb379","repo":"apache/beam","slug":"start-offset-must-not-be-none","errorCode":null,"errorMessage":"Start offset must not be 'None'","messagePattern":"Start offset must not be 'None'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/io/range_trackers.py","lineNumber":54,"sourceCode":"\n_LOGGER = logging.getLogger(__name__)\n\n\nclass OffsetRangeTracker(iobase.RangeTracker):\n  \"\"\"A 'RangeTracker' for non-negative positions of type 'long'.\"\"\"\n\n  # Offset corresponding to infinity. This can only be used as the upper-bound\n  # of a range, and indicates reading all of the records until the end without\n  # specifying exactly what the end is.\n  # Infinite ranges cannot be split because it is impossible to estimate\n  # progress within them.\n  OFFSET_INFINITY = float('inf')\n\n  def __init__(self, start, end):\n    super().__init__()\n\n    if start is None:\n      raise ValueError('Start offset must not be \\'None\\'')\n    if end is None:\n      raise ValueError('End offset must not be \\'None\\'')\n    assert isinstance(start, int)\n    if end != self.OFFSET_INFINITY:\n      assert isinstance(end, int)\n\n    assert start <= end\n\n    self._start_offset = start\n    self._stop_offset = end\n\n    self._last_record_start = -1\n    self._last_attempted_record_start = -1\n    self._offset_of_last_split_point = -1\n    self._lock = threading.Lock()\n\n    self._split_points_seen = 0\n    self._split_points_unclaimed_callback = None","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/io/range_trackers.py#L36-L72","documentation":"OffsetRangeTracker tracks progress over an integer offset range and cannot represent a None start. Its constructor validates immediately that start is an int (not None), raising ValueError otherwise. A None start would break position comparison and resume logic.","triggerScenarios":"OffsetRangeTracker(None, end) — constructing the tracker with a None start offset, typically because a source read produced no valid range.","commonSituations":"Custom BatchedSource/RestrictionSource implementations computing offsets from parsed input where the start came back None (missing file header, failed int conversion swallowed upstream).","solutions":["Pass an explicit integer start, e.g. OffsetRangeTracker(0, end).","Fix the upstream code that computes the start offset so it never yields None.","Validate/guard inputs before constructing the tracker: assert start_offset is not None."],"exampleFix":"// before\nOffsetRangeTracker(start_offset, end_offset)  # start_offset is None\n// after\nOffsetRangeTracker(start_offset if start_offset is not None else 0, end_offset)","handlingStrategy":"type-guard","validationCode":"assert start_offset is not None, \"start offset required\"\nassert isinstance(start_offset, int)","typeGuard":"def is_valid_offset(x) -> bool:\n    return isinstance(x, int) and not isinstance(x, bool)","tryCatchPattern":"try:\n    tracker = OffsetRangeTracker(start, end)\nexcept ValueError as e:\n    logger.error(\"Bad offset range: %s\", e)\n    tracker = OffsetRangeTracker(0, end)","preventionTips":["Use OffsetRangeTracker.OFFSET_INFINITY semantics only for the end, never None for start.","Validate source-computed offsets before constructing trackers.","Default missing starts to 0 where semantics allow."],"tags":["python","apache-beam","io","range-tracker","null"],"backgroundTag":"null-argument","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}