apache/beam · error · ValueError
The first record [starting at ] must be at a split point
Error message
The first record [starting at %d] must be at a split point
What it means
The very first record claimed in ordered-record mode must be flagged as a split point, because it defines the initial boundary of the range being processed. If the first claim arrives with split_point=False (last_record_start still -1), the tracker cannot establish its start boundary and raises this ValueError.
Solutions
- Mark the first claimed record as a split point: try_claim(first_offset) followed by set_current_position(first_offset, split_point=True).
- Ensure the source marks the initial record position as a split point before reading subsequent records.
- Review OffsetRangeTracker docs/usage in existing Beam sources and mirror their first-record handling.
Example fix
// before tracker.try_claim(first_offset) tracker.set_current_position(first_offset, split_point=False) // after tracker.try_claim(first_offset) tracker.set_current_position(first_offset, split_point=True)
Defensive patterns
Strategy: validation
Validate before calling
is_first = tracker._last_record_start == -1 tracker.try_claim(first_offset) tracker.set_current_position(first_offset, split_point=is_first)
Type guard
def needs_initial_split_point(tracker) -> bool:
return tracker._last_record_start == -1 Try / catch
try:
tracker.try_claim(first_offset)
tracker.set_current_position(first_offset, split_point=False)
except ValueError as e:
if 'must be at a split point' in str(e):
tracker.set_current_position(first_offset, split_point=True) Prevention
- Always mark the first record of a range as a split point.
- Follow the pattern used by built-in Beam sources when wrapping OffsetRangeTracker.
- Unit-test the first-claim path of custom sources.
When it happens
Trigger: First call to try_claim(offset) / set_current_position(offset, split_point=False) on a fresh tracker (offset_of_last_split_point == -1 and last_record_start == -1).
Common situations: Custom source implementations that forget split_point=True on the first record after opening a file/stream; code paths that initialize the tracker mid-stream without marking the initial boundary.
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
- Record at a split point has same offset as the previous…
- Trying to return a record
- End offset must not be 'None'
- Start offset must not be 'None'
- This function must only be called under the lock self.lock.
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/408415c50fa805af.
Report an issue: GitHub.
Appendix: source
Thrown at sdks/python/apache_beam/io/range_trackers.py:113
# This function must only be called under the lock self.lock.
if not self._lock.locked():
raise ValueError(
'This function must only be called under the lock self.lock.')
if record_start < self._last_record_start:
raise ValueError(
'Trying to return a record [starting at %d] which is before the '
'last-returned record [starting at %d]' %
(record_start, self._last_record_start))
if (split_point and self._offset_of_last_split_point != -1 and
record_start == self._offset_of_last_split_point):
raise ValueError(
'Record at a split point has same offset as the previous split '
'point: %d' % record_start)
if not split_point and self._last_record_start == -1:
raise ValueError(
'The first record [starting at %d] must be at a split point' %
record_start)
def try_claim(self, record_start):
with self._lock:
# Attempted claim should be monotonous.
if record_start <= self._last_attempted_record_start:
raise ValueError(
'Trying to return a record [starting at %d] which is not greater'
'than the last-attempted record [starting at %d]' %
(record_start, self._last_attempted_record_start))
self._validate_record_start(record_start, True)
self._last_attempted_record_start = record_start
if record_start >= self.stop_position():
return False
self._offset_of_last_split_point = record_start
self._last_record_start = record_start
self._split_points_seen += 1View on GitHub (pinned to 12126d8942)