`global_substream_cursor`: configured `lookback_window` not applied to `should_be_synced` filter, causing silent data loss
Maintainers usually reply within 1 day
@bazarnov is already working on this.
Since Sep 28, 2026.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 55/100
Research direction
Trace ConcurrentPerPartitionCursor._get_cursor() and _create_cursor() in airbyte_cdk/sources/declarative/incremental/concurrent_partition_cursor.py, then follow ConcurrentCursor.init() and should_be_synced() in airbyte_cdk/sources/streams/concurrent/cursor.py. Compare the configured DatetimeBasedCursor lookback with the runtime lookback, and verify that a one-day window prevents records just behind the global cursor from being dropped.
Written by the indexing model from the issue text.
Description
Community report
Reported by a user in the Airbyte community Slack: https://airbytehq.slack.com/archives/C021JANJ6TY/p1776936626673869
The user has an Intercom connector with hourly syncs, lookback_window = 1 day, and conversation_parts in incremental append+deduped mode. They found that statistics.count_conversation_parts on conversations consistently exceeds the number of parts in their destination table. Manual verification against the Intercom API confirmed the API returns the correct number of parts but Airbyte drops some during sync.
Summary
When a substream uses global_substream_cursor: true + is_client_side_incremental: true, the configured lookback_window on the DatetimeBasedCursor is not applied to the should_be_synced client-side filter. Instead, the filter uses the runtime_lookback_window (previous sync's duration in seconds), which is typically only a few minutes. This causes parts whose updated_at is even slightly behind the global cursor to be silently dropped.
Affected code path
-
ConcurrentPerPartitionCursor._get_cursor()creates a cursor usingself._lookback_window, which is the runtime lookback (previous sync duration in seconds), loaded from state atairbyte_cdk/sources/declarative/incremental/concurrent_partition_cursor.pyline 474:self._lookback_window = int(stream_state.get("lookback_window", 0)) -
This value is passed to
_create_cursor()at line 596-600:if self._use_global_cursor: return self._create_cursor( self._global_cursor, self._lookback_window if self._global_cursor else 0, ) -
In
model_to_component_factory.pylines 1416-1426, the runtime lookback is subtracted from the cursor value:stream_state_value = stream_state.get(cursor_field.cursor_field_key) if runtime_lookback_window and stream_state_value: new_stream_state = ( connector_state_converter.parse_timestamp(stream_state_value) - runtime_lookback_window ) -
This becomes
self.startinConcurrentCursor.__init__()atairbyte_cdk/sources/streams/concurrent/cursor.pyline 225. -
should_be_synced()at line 561-573 usesself.startas the lower bound:return self.start <= record_cursor_value <= self._end_provider()
The configured lookback_window (e.g., P1D) from the manifest's DatetimeBasedCursor is passed to ConcurrentCursor as self._lookback_window (line 226) but is only used in stream_slices() for generating API request time ranges. It is never referenced by should_be_synced().
Reproduction
Connector: source-intercom
Stream: conversation_parts (substream of conversations)
Config: lookback_window = 1, hourly syncs, incremental append+deduped
Manifest settings: global_substream_cursor: true, is_client_side_incremental: true
Observed behavior:
- Intercom API returns 44 parts for a conversation
- Airbyte destination table has 38 parts (6 missing)
- The 6 missing parts were created seconds/minutes before other parts on the same conversation that were successfully synced
- Example: On April 28, parts created at 15:33:10-16 were dropped, while parts created at 15:33:18 (2 seconds later) on the same conversation were synced. The
should_be_syncedcutoff landed between them.
Expected behavior:
- With a 1-day
lookback_window,self.startshould beglobal_cursor - 1 day, notglobal_cursor - previous_sync_duration_seconds - All 44 parts should be synced
Root cause
The global_substream_cursor tracks one cursor across all partitions. When the cursor advances (from processing parts on other conversations), parts from individual conversations that have updated_at slightly behind the global max are dropped by should_be_synced. The runtime lookback (a few minutes) is far too small to cover the spread of updated_at values across conversations in a workspace.
Suggested fix
Apply the configured lookback_window (from the manifest's DatetimeBasedCursor) to the should_be_synced filter when using global_substream_cursor, either instead of or in addition to the runtime lookback. The effective lookback should be max(configured_lookback_window, runtime_lookback_window).
Devin session — requested by @mark-grivnin
- Dominant language
- Python
- Stars
- 27
- Forks
- 56
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 26
Getting set up
- Ships a Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from airbytehq/airbyte-python-cdk
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
airbytehq/airbyte-python-cdk#1080 ·
Maintainers usually reply within 1 day
-
community
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
airbytehq/airbyte-python-cdk#782 ·
Maintainers usually reply within 1 day
-
community
Difficulty 5/5 Over a week Newbie friendliness 35/100
airbytehq/airbyte-python-cdk#1148 · 1 comment ·
Maintainers usually reply within 1 day
-
community
Difficulty 2/5 1-3 hours Newbie friendliness 45/100
airbytehq/airbyte-python-cdk#1146 · 1 comment · 3 reactions ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 4/5 3-5 days Newbie friendliness 48/100
airbytehq/airbyte-python-cdk#1120 · 2 comments ·
Maintainers usually reply within 1 day
All issues in airbytehq/airbyte-python-cdk
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
eclipse-score/inc_someip_gateway#356 ·
Maintainers usually reply within 2 days
-
[BUG]: `GitHubRestAdapter.check_user_permission` and `has_write_access` raise `TypeError` (missing `params`), silently disabling the inactivity lifecyclePossibly taken @tarunagnihotri534 claimed this today. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 67/100
AOSSIE-Org/Gitcord-GithubDiscordBot#167 · 1 comment ·
Maintainers usually reply within 2 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
mihai-vlc/sublime-jsfmt#56 ·
-
comp/cli comp/plugins P2 type/bug
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
NousResearch/hermes-agent#141917 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 78/100
riscv/riscv-opcodes#441 ·