Skip to content

Conversation

@darynaishchenko
Copy link
Contributor

@darynaishchenko darynaishchenko commented Jan 29, 2026

Summary

Introduces a new REFRESH_TOKEN_THEN_RETRY response action type that forces an OAuth token refresh before retrying a request. This is useful when APIs return 401 Unauthorized but the stored token expiry hasn't been reached yet (the token was invalidated server-side before its expected expiration).

This generalizes the pattern from PR #72309 (HubSpot custom component) into the CDK itself.

Key changes:

  • Added REFRESH_TOKEN_THEN_RETRY to ResponseAction enum
  • Updated HttpClient._handle_error_resolution to force token refresh by directly calling refresh_access_token() (bypassing expiry check)
  • Added the new action to declarative_component_schema.yaml for declarative connectors
  • Regenerated Pydantic models from schema
  • Added 4 unit tests covering: successful refresh, non-OAuth authenticators, refresh failures, and end-to-end retry success

Updates since last revision:

  • Changed log level from debug to warning for the case when REFRESH_TOKEN_THEN_RETRY is received but the authenticator doesn't support token refresh (per reviewer feedback)
  • Updated corresponding test to verify warning log level instead of debug

Review & Testing Checklist for Human

  • Verify the token refresh logic correctly calls refresh_access_token() directly (not get_access_token()) to force refresh regardless of stored expiry time - this was the key requirement
  • Review the hasattr checks at http_client.py:460-463 - ensure they correctly identify OAuth authenticators that support token refresh
  • Check the broad except Exception at line 470 - this is intentional for resilience but may mask unexpected errors
  • Test with an actual OAuth-based connector (e.g., HubSpot) to verify end-to-end behavior when a 401 is returned

Recommended test plan:

  1. Configure a connector with OAuth authentication (e.g., source-hubspot)
  2. Manually invalidate the token server-side or simulate a 401 response
  3. Verify the connector successfully refreshes the token and retries the request

Notes

Requested by @darynaishchenko

Link to Devin run: https://app.devin.ai/sessions/3ce943802e5743bead2f54d850ae8455

Summary by CodeRabbit

  • New Features

    • Added a REFRESH_TOKEN_THEN_RETRY response action so the client can refresh OAuth tokens and transparently retry requests on authentication failures.
  • Tests

    • Added unit tests covering token refresh and retry flows: successful refresh, refresh failures, single-use/renewal scenarios, and behavior when no OAuth authenticator is present.

✏️ Tip: You can customize this high-level summary in your review settings.

…en refresh

This introduces a new ResponseAction type called REFRESH_TOKEN_THEN_RETRY that
forces an OAuth token refresh before retrying a request. This is useful when
APIs return 401 Unauthorized but the stored token expiry hasn't been reached yet.

Changes:
- Add REFRESH_TOKEN_THEN_RETRY to ResponseAction enum
- Update HttpClient._handle_error_resolution to force token refresh when this
  action is encountered by directly calling refresh_access_token()
- Add REFRESH_TOKEN_THEN_RETRY to declarative_component_schema.yaml
- Regenerate Pydantic models from schema
- Add unit tests for the new functionality

This generalizes the pattern from PR #72309 (HubSpot custom component) into
the CDK itself.

Co-Authored-By: Daryna Ishchenko <darina.ishchenko17@gmail.com>
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@github-actions
Copy link

👋 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@devin/1769697982-refresh-token-then-retry#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 devin/1769697982-refresh-token-then-retry

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.

Co-Authored-By: Daryna Ishchenko <darina.ishchenko17@gmail.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 29, 2026

📝 Walkthrough

Walkthrough

Adds a new response action REFRESH_TOKEN_THEN_RETRY across schema, models, response enums, HTTP client, OAuth authenticators, and unit tests; the HTTP client will attempt to call the authenticator's refresh_and_set_access_token and then raise a backoff to retry when that action is triggered.

Changes

Cohort / File(s) Summary
Schema & Declarative Models
airbyte_cdk/sources/declarative/declarative_component_schema.yaml, airbyte_cdk/sources/declarative/models/declarative_component_schema.py
Added REFRESH_TOKEN_THEN_RETRY to the action enum of HttpResponseFilter and updated examples.
Response Models
airbyte_cdk/sources/streams/http/error_handlers/response_models.py
Added REFRESH_TOKEN_THEN_RETRY enum member to ResponseAction.
HTTP Client
airbyte_cdk/sources/streams/http/http_client.py
Included REFRESH_TOKEN_THEN_RETRY in retryable actions and _handle_error_resolution: attempt session.auth.refresh_and_set_access_token() if available, log outcomes, and treat as a backoff-triggering action (retry flow same as RETRY/RATE_LIMITED).
OAuth Authenticators
airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py, airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py
Added public refresh_and_set_access_token() to AbstractOauth2Authenticator and Oauth2Authenticator; moved inline refresh logic into this helper and updated get_access_token() to use it.
Unit Tests
unit_tests/sources/streams/http/test_http_client.py
Added MockOAuthAuthenticator test utility and tests covering refresh-and-retry flows (refresh invoked, missing authenticator fallback, refresh failure handling, single-use refresh-token case, and retry-success after refresh).

Sequence Diagram

sequenceDiagram
    participant Client
    participant HttpClient
    participant OAuthAuth as OAuth Authenticator
    participant Backoff as Backoff Handler

    Client->>HttpClient: Send request
    HttpClient->>HttpClient: Execute request
    HttpClient-->>HttpClient: Receive 401 -> response filter => REFRESH_TOKEN_THEN_RETRY
    alt OAuth authenticator present
        HttpClient->>OAuthAuth: refresh_and_set_access_token()
        OAuthAuth-->>HttpClient: (updates token & expiry or raises)
        HttpClient->>Backoff: raise DefaultBackoffException (retry)
    else No oauth authenticator or refresh fails
        HttpClient->>HttpClient: log warning
        HttpClient->>Backoff: raise DefaultBackoffException (retry)
    end
    Backoff->>HttpClient: retry request (after backoff)
    HttpClient->>HttpClient: Execute request (with updated token if available)
    HttpClient-->>Client: Return response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Would you like me to suggest additional tests for concurrent refresh attempts or token race conditions, wdyt?

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature being added: a new REFRESH_TOKEN_THEN_RETRY response action for OAuth token refresh, which aligns with the substantial changes across schema, model, and client implementations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

@darynaishchenko
Copy link
Contributor Author

darynaishchenko commented Jan 29, 2026

/autofix

Auto-Fix Job Info

This job attempts to auto-fix any linting or formating issues. If any fixes are made,
those changes will be automatically committed and pushed back to the PR.

Note: This job can only be run by maintainers. On PRs from forks, this command requires
that the PR author has enabled the Allow edits from maintainers option.

PR auto-fix job started... Check job output.

🟦 Job completed successfully (no changes).

@github-actions
Copy link

github-actions bot commented Jan 29, 2026

PyTest Results (Fast)

3 844 tests  +5   3 832 ✅ +5   6m 37s ⏱️ -11s
    1 suites ±0      12 💤 ±0 
    1 files   ±0       0 ❌ ±0 

Results for commit 5699bdd. ± Comparison against base commit 313db66.

♻️ This comment has been updated with latest results.

@github-actions
Copy link

github-actions bot commented Jan 29, 2026

PyTest Results (Full)

3 847 tests  +5   3 835 ✅ +5   11m 24s ⏱️ +22s
    1 suites ±0      12 💤 ±0 
    1 files   ±0       0 ❌ ±0 

Results for commit 5699bdd. ± Comparison against base commit 313db66.

♻️ This comment has been updated with latest results.

devin-ai-integration bot and others added 2 commits January 29, 2026 16:35
…or message

Co-Authored-By: Daryna Ishchenko <darina.ishchenko17@gmail.com>
Co-Authored-By: Daryna Ishchenko <darina.ishchenko17@gmail.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@airbyte_cdk/sources/streams/http/http_client.py`:
- Around line 469-472: The unpacking of refresh_access_token() assumes a 2-tuple
but Oauth2Authenticator returns 3 values; change the unpacking at the call site
in http_client.py (the try block that calls
self._session.auth.refresh_access_token()) to use extended unpacking (e.g.,
token, expires_in, *_ = self._session.auth.refresh_access_token()) so it safely
handles both 2- and 3-value returns, then use token and expires_in as before
when setting self._session.auth.access_token and calling
self._session.auth.set_token_expiry_date(expires_in).
🧹 Nitpick comments (1)
unit_tests/sources/streams/http/test_http_client.py (1)

842-857: Consider adding a mock for the 3-tuple return variant as well.

The MockOAuthAuthenticator returns 2 values from refresh_access_token(), which matches AbstractOauth2Authenticator. Once the http_client code is updated to handle both 2 and 3 value returns, it might be worth adding a test with a mock that returns 3 values (like Oauth2Authenticator does) to ensure both variants work correctly, wdyt?

… refresh_access_token

Co-Authored-By: Daryna Ishchenko <darina.ishchenko17@gmail.com>
…or to persist new refresh token

Co-Authored-By: Daryna Ishchenko <darina.ishchenko17@gmail.com>
Comment on lines 470 to 474
# Use extended unpacking to handle both 2-tuple (AbstractOauth2Authenticator)
# and 3-tuple (Oauth2Authenticator which also returns refresh_token) returns
token, expires_in, *_ = self._session.auth.refresh_access_token() # type: ignore[union-attr]
self._session.auth.access_token = token # type: ignore[union-attr]
self._session.auth.set_token_expiry_date(expires_in) # type: ignore[union-attr]
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we move this logic into the OAuth authenticator itself as a new method, for example, refresh_and_set_access_token, instead of handling it here?

Copy link
Contributor

Choose a reason for hiding this comment

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

That's a good suggestion! Moving this logic into the OAuth authenticator as a new method like refresh_and_set_access_token() would provide better encapsulation and separation of concerns.

The authenticator already has get_access_token() which does similar logic (refresh + set token + set expiry + emit control message for single-use tokens), but it checks token_has_expired() first. A new method could essentially be get_access_token() without the expiry check.

@darynaishchenko - what do you think? Should we refactor this to add a new method to the authenticator interface instead of handling it in HttpClient?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good point, please proceed with this

Copy link
Contributor

Choose a reason for hiding this comment

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

Done! I've refactored the code to add a refresh_and_set_access_token() method to AbstractOauth2Authenticator and overridden it in SingleUseRefreshTokenOauth2Authenticator to handle the additional logic (persisting refresh token + emitting control message).

Now HttpClient simply calls refresh_and_set_access_token() without needing to know about the authenticator internals. This provides better encapsulation and separation of concerns.

devin-ai-integration bot and others added 2 commits January 29, 2026 16:53
Co-Authored-By: Daryna Ishchenko <darina.ishchenko17@gmail.com>
…thod in authenticator

Co-Authored-By: Daryna Ishchenko <darina.ishchenko17@gmail.com>
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.

3 participants