Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/api/connectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,25 @@ async def compute_orphans_for_connector_type(
for conn in active:
try:
connector = await connector_service.get_connector(conn.connection_id)
if not connector or not connector.is_authenticated:
if not connector:
logger.info(
"Skipping orphan compute — connection unauthenticated",
connector_type=connector_type,
connection_id=conn.connection_id,
)
return None

# Always re-authenticate before making API calls so that stale
# cached access tokens (Google tokens last 1 hour) are refreshed.
# This mirrors the pattern used in connector_sync.
if not await connector.authenticate():
logger.info(
"Skipping orphan compute — re-authentication failed",
connector_type=connector_type,
connection_id=conn.connection_id,
)
return None

# Drive the per-id existence check via cfg.file_ids when the
# connector supports it (SharePoint / OneDrive / Google Drive).
# The flat default of list_files() only returns the *root* listing
Expand Down Expand Up @@ -235,6 +246,7 @@ async def compute_orphans_for_connector_type(
connector_type=connector_type,
connection_id=conn.connection_id,
error=str(e),
error_type=type(e).__name__,
)
return None

Expand Down
10 changes: 9 additions & 1 deletion src/connectors/google_drive/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,15 @@ async def load_credentials(self) -> Credentials | None:
except Exception as e:
logger.debug("[GoogleDrive] load_credentials: token refresh failed: %s", e)
self.creds = None
self._remove_token_file()
# Only wipe the token file for permanent failures (revoked or
# invalid grant). Transient failures (network timeout, etc.)
# should not force the user through re-authentication.
error_str = str(e).lower()
is_permanent = any(
k in error_str for k in ("invalid_grant", "revoked", "unauthorized_client")
)
if is_permanent:
self._remove_token_file()
raise ValueError(
f"Failed to refresh Google Drive credentials. "
f"The refresh token may have expired or been revoked. "
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/api/test_reconcile_orphans_for_connector_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def _make_connection(connection_id: str, is_active: bool = True):
def _make_connector(remote_file_ids, *, authenticated=True, raise_on_list=False):
connector = MagicMock()
connector.is_authenticated = authenticated
connector.authenticate = AsyncMock(return_value=authenticated)
if raise_on_list:
connector.list_files = AsyncMock(side_effect=RuntimeError("graph 503"))
else:
Expand Down Expand Up @@ -315,6 +316,7 @@ async def test_paginated_listing_aggregates_all_pages():
conn = _make_connection("c1")
connector = MagicMock()
connector.is_authenticated = True
connector.authenticate = AsyncMock(return_value=True)
pages = [
{"files": [{"id": "a"}], "nextPageToken": "tok-1"},
{"files": [{"id": "b"}, {"id": "c"}]},
Expand Down
Loading