Skip to content

fix(declarative): ignore full-refresh sentinel in parent state seeding - #1079

Open
Ryan Waskewich (rwask) wants to merge 1 commit into
mainfrom
rwask/fix-full-refresh-sentinel-parent-state
Open

fix(declarative): ignore full-refresh sentinel in parent state seeding#1079
Ryan Waskewich (rwask) wants to merge 1 commit into
mainfrom
rwask/fix-full-refresh-sentinel-parent-state

Conversation

@rwask

@rwask Ryan Waskewich (rwask) commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Converting a previously full-refresh stream to incremental with an incremental_dependency parent crashes cursor initialization whenever the connection has legacy state. Full refresh streams checkpoint {"__ab_no_cursor_state_message": true}; the single-value fallback in _instantiate_parent_stream_state_manager re-keys that boolean under the parent's cursor field (producing e.g. {"created_at": true}), and DatetimeStreamStateConverter.parse_value raises ValueError: No format in [...] matching True before any records are read. Every existing connection with such a stream selected fails on its next sync after upgrade — regardless of configured sync mode — until its state is reset.

This filters the NO_CURSOR_STATE_KEY sentinel out of the child state in create_parent_stream_config_with_substream_wrapper before it is used to seed parent stream state, so a converted stream starts its first incremental sync from empty state, matching how abstract_source.py already discards the sentinel.

Found while validating airbytehq/airbyte#72784 (source-zoom), which had to revert its full-refresh → incremental conversion of existing streams because of this crash.

Note: SubstreamPartitionRouter._migrate_child_state_to_parent_state has the same boolean-passthrough flaw but currently has no callers, so it is left untouched here.

Test plan

  • New unit test test_create_concurrent_cursor_from_perpartition_cursor_ignores_full_refresh_sentinel_state: on current main it reproduces the crash (ValueError: No format in ['%Y-%m-%dT%H:%M:%S.%f%z'] matching True); with the fix it passes
  • poetry run pytest unit_tests/sources/declarative/parsers/ unit_tests/sources/declarative/partition_routers/ — 237 passed
  • ruff format --check and ruff check clean on changed files
  • End-to-end repro: airbyte/source-zoom:1.3.0-preview.6a84bb0 (which declares incremental + incremental_dependency on previously full-refresh meeting streams) exits 1 on read when given {"__ab_no_cursor_state_message": true} stream state; the same read succeeds with this change applied to the installed CDK

Summary by CodeRabbit

  • Bug Fixes

    • Fixed an issue where full-refresh state could be incorrectly carried into incremental substream cursor initialization.
    • Ensured nested streams preserve correct cursor state when transitioning between full-refresh and incremental behavior.
  • Tests

    • Added coverage verifying that full-refresh sentinel values are not converted into invalid parent cursor state.

Full refresh streams checkpoint {"__ab_no_cursor_state_message": true}. When a
later connector version converts such a stream to incremental with an
incremental_dependency parent, the single-value fallback in
_instantiate_parent_stream_state_manager re-keys the sentinel's boolean under
the parent's cursor field and cursor initialization crashes with
ValueError: No format in [...] matching True, before any records are read.

Filter the sentinel out of the child state before seeding parent state so the
converted stream starts its first incremental sync from empty state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

👋 Greetings, Airbyte Team Member!

Here are some helpful tips and reminders for your convenience.

💡 Show Tips and Tricks

Testing This CDK Version

You can test this version of the CDK using the following:

# Run the CLI from this branch:
uvx 'git+https://github.com/airbytehq/airbyte-python-cdk.git@rwask/fix-full-refresh-sentinel-parent-state#egg=airbyte-python-cdk[dev]' --help

# Update a connector to use the CDK from this branch ref:
cd airbyte-integrations/connectors/source-example
poe use-cdk-branch rwask/fix-full-refresh-sentinel-parent-state

PR Slash Commands

Airbyte Maintainers can execute the following slash commands on your PR:

  • /autofix - Fixes most formatting and linting issues
  • /poetry-lock - Updates poetry.lock file
  • /test - Runs connector tests with the updated CDK
  • /prerelease - Triggers a prerelease publish with default arguments
  • /poe build - Regenerate git-committed build artifacts, such as the pydantic models which are generated from the manifest JSON schema in YAML.
  • /poe <command> - Runs any poe command in the CDK environment
📚 Show Repo Guidance

Helpful Resources

📝 Edit this welcome message.

@github-actions

Copy link
Copy Markdown

PyTest Results (Fast)

4 134 tests  +1   4 122 ✅ +1   7m 47s ⏱️ +12s
    1 suites ±0      12 💤 ±0 
    1 files   ±0       0 ❌ ±0 

Results for commit df07154. ± Comparison against base commit 7e96546.

@github-actions

Copy link
Copy Markdown

PyTest Results (Full)

4 137 tests  +1   4 125 ✅ +1   12m 26s ⏱️ +3s
    1 suites ±0      12 💤 ±0 
    1 files   ±0       0 ❌ ±0 

Results for commit df07154. ± Comparison against base commit 7e96546.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The factory now removes the full-refresh cursor sentinel before initializing parent stream state during substream construction. A regression test verifies incremental dependency setup does not convert the sentinel into a parent cursor value.

Changes

Full-refresh sentinel filtering

Layer / File(s) Summary
Filter sentinel state during parent initialization
airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py, unit_tests/sources/declarative/parsers/test_model_to_component_factory.py
The factory removes the full-refresh sentinel before deriving parent cursor state, and the test verifies that incremental dependency initialization does not retain True cursor values.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: aaronsteers

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main fix: filtering the full-refresh sentinel before seeding parent state.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rwask/fix-full-refresh-sentinel-parent-state

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@unit_tests/sources/declarative/parsers/test_model_to_component_factory.py`:
- Around line 4067-4070: Update the assertion in the parent cursor state test to
require parent_cursor_state to equal an empty dictionary exactly, replacing the
permissive all(...) check while preserving the existing cursor-state setup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: abf7a35e-c0c6-4694-85ef-c6ab788072fc

📥 Commits

Reviewing files that changed from the base of the PR and between 7e96546 and df07154.

📒 Files selected for processing (2)
  • airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py
  • unit_tests/sources/declarative/parsers/test_model_to_component_factory.py

Comment on lines +4067 to +4070
parent_cursor_state = stream.cursor._partition_router.parent_stream_configs[
0
].stream.cursor.state
assert all(value is not True for value in parent_cursor_state.values())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the required empty parent state exactly.

Could we assert parent_cursor_state == {} instead? all(...) also passes for any non-boolean residual state, so it does not enforce the intended first incremental sync with empty state, wdyt?

Proposed test update
-    assert all(value is not True for value in parent_cursor_state.values())
+    assert parent_cursor_state == {}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
parent_cursor_state = stream.cursor._partition_router.parent_stream_configs[
0
].stream.cursor.state
assert all(value is not True for value in parent_cursor_state.values())
parent_cursor_state = stream.cursor._partition_router.parent_stream_configs[
0
].stream.cursor.state
assert parent_cursor_state == {}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@unit_tests/sources/declarative/parsers/test_model_to_component_factory.py`
around lines 4067 - 4070, Update the assertion in the parent cursor state test
to require parent_cursor_state to equal an empty dictionary exactly, replacing
the permissive all(...) check while preserving the existing cursor-state setup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant