Skip to content

fix(bitbucket-cloud): tolerate denied repos, cut request budget - #2011

Merged
aleksdotbar merged 11 commits into
mainfrom
fix/bitbucket-inaccessible-repos
Jul 29, 2026
Merged

fix(bitbucket-cloud): tolerate denied repos, cut request budget#2011
aleksdotbar merged 11 commits into
mainfrom
fix/bitbucket-inaccessible-repos

Conversation

@aleksdotbar

@aleksdotbar aleksdotbar commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

A repository can appear in the workspace listing and deny every request under it (403) — routine with repo-scoped tokens, common in production. The connector failed the whole sync over it, on every run, forever. A review for other members of the same class (foreseeable reality handled as a failure) found four more; all fixed here. A second review pass targeted the other production constraint — Bitbucket's ~1,000 req/h budget vs ~1,400 repositories — and cut the per-sync request count.

Fixes

  • 403/404 on a repository → skip it, warn once, sync stays green. Recorded on the shared catalog so one discovery covers all 20 streams. Everything else (5xx, network, bad JSON) still fails loudly.
  • Vanished commit diffstat (diffstat/{sha} gone: orphaned merges, rewritten history) → tolerated; marker records the denial so the completeness gate keeps prior line counts instead of zeroing them. Previously one such commit failed its repository every sync. The pre-rewrite connector handled this (ignore_404); the rewrite lost it.
  • Branch generations per repository, not per bucket (stream + dbt model). One denied repo froze branch updates for its whole bucket; with denied repos scattered, all 8 buckets freeze and branches never update again. Now a denied repo just keeps its previous generation; neighbours update independently. Trade: branches of a deleted repo linger — bounded, better than starvation.
  • 401 aborts immediately with the cause (unset bitbucket_username → Bearer rejection, or rotated token) instead of 1,400 per-repo quarantine logs ending in a generic error.
  • Commit-range form chunked at 100 includes, full exclude set per chunk. Bitbucket's include/exclude ceiling is undocumented and surfaces as unexplained 400s (BCLOUD-13229); union of chunks is the same commit set, bronze dedups overlap.

Rate-limit cuts

  • Idle-repo gate: the repository's updated_on comes free with the workspace listing and is stored in per-repo state; unchanged → branches, commits, file_changes and reachability skip the repository without a single request. The previous branch generation simply stays the newest complete one.
  • Shared listing selections: the PR / pipeline / issue listing runs once per repository per sync. The parent stream fetches full pages and caches a slim projection (id, cursor, head hashes — ~100 bytes per entity) on the shared catalog; the other five PR streams, two pipeline streams and two issue streams reuse it. Keyed by watermark, so a stream whose state lags after a failed sync fetches its own selection instead of reusing a narrower one.

Net: an idle repository costs ~5 requests per sync instead of ~13–25.

Not changed

Feature-level fetches (comments, diffstat-per-PR, activity, tasks, issues, pipelines) already tolerated 403 via snapshot_available=false — that is what stops a denied read from being recorded as a legitimate empty collection and deleting rows. Untouched.

Tests

Matrix derives the stream list from source.streams() and runs every stream — current and future — against an all-403 repository (sync green, no items, markers unavailable, state frozen) and a hard 500 (still fails loudly). Each fix has a test that fails without it. Per-repo branch gating verified with data on ClickHouse 25.7.5: newer generation replaces, denied repo retains, no coupling. Request budget pinned by counting clients: idle repo → zero calls, changed updated_on → resync, migrated legacy state never gated, children reuse the parent's listing and emit records identical to a fresh fetch, slim cache rejects raw objects. 186 pass.

Summary by CodeRabbit

  • Bug Fixes

    • Bitbucket Cloud syncs now continue when repositories are inaccessible, while clearly distinguishing access issues from transient outages and authentication failures.
    • Missing diffstat data is handled safely, with snapshot availability reported accurately.
    • Branch snapshots remain consistent when individual repositories cannot be read.
  • Performance

    • Unchanged repositories now avoid unnecessary API requests.
    • Pull request selections are reused across related streams.
    • Large commit comparisons are processed in manageable batches.

A repository can be returned by the workspace listing and still answer 403 to
every request under it — routine with repo-scoped tokens and per-repository
permissions, and confirmed in production on virtuozzo.

Those fetches raise: refs/branches, the commit ranges, commit diffstat and the
pull-request listing all go through paginate(), which only tolerates 404. The
per-repository quarantine caught the error and let the other repositories
finish, but finish_bucket then failed the stream at the end of the sync. A 403
is permanent, so that repeated on every run: the sync stayed red forever and
buried the transient failures the quarantine exists to surface.

Split the two error classes. 403 and 404 on a repository now mark it
inaccessible on the shared catalog and skip it, logged once as a warning and
summarised at the end of the sync; every other error keeps the previous
behaviour and still fails the sync. Because the catalog is shared by all
streams, the first stream to hit the 403 saves the rest from rediscovering it.
404 is included deliberately: a repository listed at the start of a sync can be
deleted while it runs.

branches needs one extra step. It is a bucket-scoped, deletion-aware snapshot,
so a skipped repository contributing no branches would read as "every branch of
that repository was deleted" and drop live rows. Its marker is therefore
unavailable when any repository in the bucket was skipped or failed, which makes
dbt keep the previous generation. The cost is that the other repositories in
that bucket keep their previous branch generation until the denied one becomes
readable; their commits and pull requests are unaffected, being separate
streams.

This does not change snapshot_available on the optional per-entity fetches
(comments, diffstat, activity, tasks, issues, pipelines). Those already tolerate
403 and mark the generation unavailable, which is what stops a denied read from
being recorded as an empty collection and wiping previously synced rows — that
behaviour was correct and is untouched.

tests: 11 new cases pin denied-is-skipped against transient-still-fails, the
catalog hand-off between streams, and the branches marker in both directions.
Verified they catch the regression: narrowing DENIED_STATUSES to the empty set
fails 5 of them. 117 pass.

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
@aleksdotbar
aleksdotbar requested a review from a team as a code owner July 29, 2026 08:23
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Regenerate the connectors-ddl snapshot

This PR changes src/ingestion/**. If your change affects any
bronze / silver / gold schema, regenerate the committed DDL snapshot
and include it in this PR.

Prerequisites (details: src/ingestion/scripts/bootstrap-db/README.md):

  • docker + a fresh throwaway ClickHouse 25.7.5 (README "Local ClickHouse for testing")
  • .env from .env.bootstrap.example pointing at it; use the host LAN IP,
    reachable from both the host and connector containers
    (host.docker.internal does not resolve on the macOS host itself)
  • python3.12 or python3.11 on PATH (pinned dbt venv)
  • HubSpot + Salesforce credentials in .env — their discover calls the
    live APIs; without them, apply ../connectors-ddl/{hubspot,salesforce}.sql
    (relative to bootstrap-db/) to seed their bronze, then run the dbt step
cd src/ingestion/scripts/bootstrap-db
set -a; source pins.env; source .env; set +a
./bootstrap-db.sh connectors-config.yaml   # fresh ClickHouse 25.7.5
./dump-ddl.sh                              # writes scripts/connectors-ddl/*.sql

Commit the resulting scripts/connectors-ddl/*.sql diff. If nothing
changed, no snapshot update is needed. (Regeneration is manual for now.)

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Bitbucket Cloud syncs now classify repository access denials as skips, track inaccessible repositories, gate unchanged repositories, scope branch snapshots per repository, tolerate unavailable feature data, chunk commit comparisons, and share slim selections across streams.

Changes

Bitbucket Cloud reliability and request budgeting

Layer / File(s) Summary
Access denial routing
src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/client.py, .../streams/base.py, .../tests/conftest.py
Catalogs track inaccessible repositories by UUID; streams skip 403/404 repositories while preserving 401 and transient failure handling.
Repository incremental snapshots
.../streams/branches.py, .../streams/commits.py, .../streams/file_changes.py, .../streams/commit_branch_reachability.py, .../dbt/...repository_branches.sql
Incremental streams skip unchanged repositories, persist updated_on, emit repository-scoped branch markers, and select generations by repository identity.
Optional API data and commit chunking
.../client.py, .../streams/file_changes.py, .../streams/metric_events.py
Unavailable diffstat, pipeline, and test-report data is represented without marking repositories inaccessible; commit includes are sent in chunks with shared excludes.
Shared selection caches
.../streams/pr_base.py, .../streams/pull_requests.py, .../streams/metric_events.py
State streams cache slim pull-request, pipeline, and issue selections, while full-record streams fetch their own data.
Reliability and request-budget validation
.../tests/test_inaccessible_repos.py, .../tests/test_request_budget.py, .../tests/test_reliability.py, .../tests/test_commits.py, .../tests/test_file_changes.py
Tests cover denied and transient failures, credential errors, missing diffstat, commit chunking, idle gating, snapshot safety, and shared selection reuse.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Stream
  participant BitbucketClient
  participant RepositoryCatalog
  participant SnapshotModel
  Stream->>BitbucketClient: fetch repository data
  BitbucketClient-->>Stream: denied or unavailable response
  Stream->>RepositoryCatalog: mark inaccessible or record unavailable feature
  Stream-->>SnapshotModel: emit scoped records and completeness markers
  SnapshotModel-->>SnapshotModel: select latest repository generation
Loading

Possibly related PRs

Suggested reviewers: ktursunov, mitasovr

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately captures the main changes: denied repository tolerance and reduced request usage.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/bitbucket-inaccessible-repos

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

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
`@src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/base.py`:
- Around line 199-212: Update the inaccessible-repository branch in the base
stream’s record-reading loop to append each pre-known inaccessible repo to
self._skipped_repositories before continuing, matching
BranchesStream.read_records and ensuring finish_bucket includes it in the
skipped summary. Do not alter the existing BitbucketApiError classification or
other exception handling.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4df75515-33b1-4fb6-b3ab-fa29b0b869e4

📥 Commits

Reviewing files that changed from the base of the PR and between a30a79c and ed9d181.

📒 Files selected for processing (5)
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/client.py
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/base.py
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/branches.py
  • src/ingestion/connectors/git/bitbucket-cloud/tests/conftest.py
  • src/ingestion/connectors/git/bitbucket-cloud/tests/test_inaccessible_repos.py

…ve all streams

Audit of every HTTP call site found two fetches that still raised on 403 and,
via the new denied-repository handling, would have marked the whole repository
inaccessible over a feature-level denial — suppressing its commits and pull
requests because the inaccessible mark is shared across streams:

- the open-pipeline refetch (pipelines/{uuid})
- pipeline step test reports

Both are per-repository features; a 403 there means "no pipelines visible",
not "this repository is unreadable". They now tolerate 403/404 like every
other optional fetch, and the snapshot marker records the denial.

The guarantee is now enforced structurally rather than by enumeration: a test
matrix derives the stream list from SourceBitbucketCloud.streams() itself and
runs every stream — current and future — against a repository that answers 403
to everything (sync must stay green, no items emitted, every marker touching
that repository unavailable, state never advanced) and against a hard 500
(sync must still fail loudly). Two targeted tests pin the feature-level paths
the matrix cannot reach, verified to fail without the fix. 173 pass.

Two matrix findings were legitimate behaviour, asserted as such: the
repositories stream emits the denied repository's metadata (the workspace
listing was readable — only the contents are not), and empty buckets keep
available markers (their partitions contain nothing to delete).

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
@aleksdotbar
aleksdotbar enabled auto-merge July 29, 2026 08:51
@aleksdotbar
aleksdotbar disabled auto-merge July 29, 2026 08:54
…und by review

A systematic review of every call site against the operational realities the
pre-rewrite connector already knew about (its ignore_404 comment names them)
found four more members of the same class as the 403 bug — foreseeable
conditions handled as exceptional failures:

1. Vanished commit diffstat. diffstat/{sha} can be permanently gone (orphaned
   merge parents, rewritten history); it raised, so one such commit failed its
   repository on every sync forever. It now goes through the tolerant fetch and
   the snapshot marker records the denial — the completeness gate keeps what
   was known instead of zeroing the commit's line counts. The repository still
   advances past the bad commit.

2. Bucket-scoped branch generations starve at fleet scale. One denied
   repository froze branch updates for its whole bucket; with denied
   repositories scattered across buckets — reported as common in production —
   every bucket freezes and branches never update again. Generations are now
   per repository (stream and dbt model): a denied repository simply produces
   no marker and keeps its previous generation, every other repository updates
   independently. Verified on ClickHouse 25.7.5: a newer generation replaces
   (deletions intact), a denied repository retains, neighbours are unaffected.
   Trade: branches of a repository deleted from the workspace linger; bounded,
   and preferable to fleet-wide starvation.

3. Credential failure drowned in quarantine noise. A 401 is global, not
   per-repository; it was quarantined 1,400 times and surfaced as a generic
   end-of-sync error. It now aborts at the first occurrence with the actionable
   cause (unset bitbucket_username sends Atlassian tokens as Bearer; or the
   token expired/rotated).

4. Unbounded include/exclude form. Bitbucket documents no ceiling on commit
   range parameters and its limits surface as unexplained 400s (BCLOUD-13229);
   a repository with hundreds of branches sent them all in one form. Includes
   are now chunked at 100 with the full exclude set on every chunk — the union
   of the chunked ranges is the same commit set, and bronze dedups overlap.

Each fix carries a test that fails without it; the all-denied matrix now also
exercises the diffstat path. 177 pass.

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
@aleksdotbar aleksdotbar changed the title fix(bitbucket-cloud): skip repositories the token cannot read instead of failing the sync fix(bitbucket-cloud): survive unreadable repositories and kin Jul 29, 2026
@aleksdotbar aleksdotbar changed the title fix(bitbucket-cloud): survive unreadable repositories and kin fix(bitbucket-cloud): tolerate denied repositories, vanished diffstats, 401 Jul 29, 2026
A repository marked inaccessible by an earlier stream was skipped silently,
so later streams' end-of-sync summaries under-reported what they skipped.

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
@aleksdotbar
aleksdotbar enabled auto-merge July 29, 2026 09:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/ingestion/connectors/git/bitbucket-cloud/tests/test_inaccessible_repos.py (1)

313-326: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

This assertion passes for state that did advance.

The disjunction only rules out head_shas/updated_on; a PipelineStateStream subclass that wrote {"created_on": ...} for the denied repository would still satisfy it. With FullyDeniedClient nothing reaches commit_repository_state, so the strict form is both simpler and green.

♻️ Stronger, simpler assertion
-        assert stream.state["repositories"].get(repo_state_key(repo), {}) in ({}, None) or (
-            "head_shas" not in stream.state["repositories"].get(repo_state_key(repo), {})
-            and "updated_on" not in stream.state["repositories"].get(repo_state_key(repo), {})
-        ), f"{stream_class.__name__} advanced state for a repository it never read"
+        assert repo_state_key(repo) not in stream.state["repositories"], (
+            f"{stream_class.__name__} advanced state for a repository it never read"
+        )
🤖 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
`@src/ingestion/connectors/git/bitbucket-cloud/tests/test_inaccessible_repos.py`
around lines 313 - 326, Strengthen the final assertion in
test_denied_repository_state_never_advances to require that the denied
repository’s state entry is exactly absent or empty ({}/None), rather than
allowing entries containing other fields such as created_on. Keep the existing
FullyDeniedClient setup and use the repository state lookup already present in
the test.
src/ingestion/connectors/git/bitbucket-cloud/dbt/bitbucket_cloud__repository_branches.sql (1)

11-37: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Scope branch generations by repository_uuid instead of (workspace, repo_slug).

repository_uuid is emitted by the item and completion records, while Bitbucket workspaces and repo slugs can change. Renaming a repo updates those fields but leaves the old generation scoped to the stale key, so both old and new rows can map to the same or orphaned generations.

🤖 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
`@src/ingestion/connectors/git/bitbucket-cloud/dbt/bitbucket_cloud__repository_branches.sql`
around lines 11 - 37, The generations and latest CTEs currently scope branches
by workspace and repo_slug; update both grouping and selected repository
identity fields to use repository_uuid from the records instead. Keep tenant_id,
source_id, generation completion, and latest-generation selection behavior
unchanged while ensuring renamed repositories remain associated with their
existing generations.
🤖 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
`@src/ingestion/connectors/git/bitbucket-cloud/dbt/bitbucket_cloud__repository_branches.sql`:
- Line 53: Update the join with latest in the repository branches query to avoid
nullable workspace and repo_slug keys. Match generations using the deterministic
non-NULL repository_uuid together with generation_id, or use ClickHouse’s
isNotDistinctFrom semantics for nullable keys, while preserving tenant and
source scoping.

In
`@src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/client.py`:
- Around line 267-273: Update the commit pagination logic in the relevant method
to return immediately whenever includes is empty, regardless of excludes. Remove
the excludes-only paginate call, while preserving the existing return when both
includes and excludes are empty and normal pagination when includes is present.

---

Nitpick comments:
In
`@src/ingestion/connectors/git/bitbucket-cloud/dbt/bitbucket_cloud__repository_branches.sql`:
- Around line 11-37: The generations and latest CTEs currently scope branches by
workspace and repo_slug; update both grouping and selected repository identity
fields to use repository_uuid from the records instead. Keep tenant_id,
source_id, generation completion, and latest-generation selection behavior
unchanged while ensuring renamed repositories remain associated with their
existing generations.

In
`@src/ingestion/connectors/git/bitbucket-cloud/tests/test_inaccessible_repos.py`:
- Around line 313-326: Strengthen the final assertion in
test_denied_repository_state_never_advances to require that the denied
repository’s state entry is exactly absent or empty ({}/None), rather than
allowing entries containing other fields such as created_on. Keep the existing
FullyDeniedClient setup and use the repository state lookup already present in
the test.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d11a7cf6-76a3-438d-9343-82b59aee4eee

📥 Commits

Reviewing files that changed from the base of the PR and between ed9d181 and 17e80d9.

📒 Files selected for processing (8)
  • src/ingestion/connectors/git/bitbucket-cloud/dbt/bitbucket_cloud__repository_branches.sql
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/client.py
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/base.py
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/branches.py
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/file_changes.py
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/metric_events.py
  • src/ingestion/connectors/git/bitbucket-cloud/tests/test_file_changes.py
  • src/ingestion/connectors/git/bitbucket-cloud/tests/test_inaccessible_repos.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/base.py

Comment thread src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/client.py Outdated
@aleksdotbar
aleksdotbar disabled auto-merge July 29, 2026 10:16
Bitbucket's ~1,000 req/h budget cannot cover ~1,400 repositories when
every stream re-lists everything each sync:

- idle gate: repositories whose updated_on (free with the workspace
  listing) is unchanged since the last pass cost zero requests in the
  push-driven streams (branches, commits, file_changes, reachability)
- shared selections: PR / pipeline / issue listings run once per
  repository per sync; the six PR streams, three pipeline streams and
  three issue streams reuse a slim projection (id, cursor, head hashes;
  ~100 bytes per entity) cached on the shared catalog, keyed by
  watermark so a lagging stream still fetches its own

An idle repository now costs ~5 requests per sync instead of ~13-25.

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
@aleksdotbar aleksdotbar changed the title fix(bitbucket-cloud): tolerate denied repositories, vanished diffstats, 401 fix(bitbucket-cloud): tolerate denied repos, cut request budget Jul 29, 2026
@aleksdotbar
aleksdotbar enabled auto-merge July 29, 2026 10:49
POST /commits falls back to every branch when the form carries no
include, so a repository whose branches have all disappeared paged its
entire history back out under the exclude set. No current head means
nothing is newly reachable.

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/commits.py (1)

17-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Idle-gate check duplicated across four streams. The same repo_updated_on-vs-prior guard (byte-identical, including the comment, in three of the four files) is reimplemented separately instead of living once on BitbucketIncrementalStream. A future correction to this gating rule (e.g. a subtle edge case in how updated_on is compared) would need to be applied in four places and could easily be missed in one.

  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/commits.py#L17-L24: extract this block into a shared base helper, e.g. BitbucketIncrementalStream.repository_is_idle(repo, prior) -> bool.
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/file_changes.py#L17-L24: replace with a call to the same shared helper.
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/commit_branch_reachability.py#L18-L25: replace with a call to the same shared helper.
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/branches.py#L33-L36: replace with a call to the same shared helper (this site already differs slightly from the other three and would become consistent).
🤖 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
`@src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/commits.py`
around lines 17 - 24, Move the shared repo_updated_on-versus-prior idle check
into BitbucketIncrementalStream as repository_is_idle(repo, prior), preserving
its existing comparison semantics. In
src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/commits.py#17-24,
file_changes.py#17-24, commit_branch_reachability.py#18-25, and
branches.py#33-36, replace each duplicated guard with the shared helper and
retain the existing early-return behavior.
🤖 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
`@src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/metric_events.py`:
- Around line 124-139: Update PipelineStateStream.pipeline_candidates to include
the prior open-pipeline state in cache_key alongside repo_state_key(repo) and
created_on. Ensure equivalent open lists produce a stable key and different open
states cannot share cached selections, while preserving the existing cache
lookup, fetch, and state-return behavior.

---

Nitpick comments:
In
`@src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/commits.py`:
- Around line 17-24: Move the shared repo_updated_on-versus-prior idle check
into BitbucketIncrementalStream as repository_is_idle(repo, prior), preserving
its existing comparison semantics. In
src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/commits.py#17-24,
file_changes.py#17-24, commit_branch_reachability.py#18-25, and
branches.py#33-36, replace each duplicated guard with the shared helper and
retain the existing early-return behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 042adb8a-a169-45ee-9159-59b175680a70

📥 Commits

Reviewing files that changed from the base of the PR and between 17e80d9 and 4926230.

📒 Files selected for processing (13)
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/client.py
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/branches.py
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/commit_branch_reachability.py
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/commits.py
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/file_changes.py
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/metric_events.py
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/pr_base.py
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/streams/pull_requests.py
  • src/ingestion/connectors/git/bitbucket-cloud/tests/conftest.py
  • src/ingestion/connectors/git/bitbucket-cloud/tests/test_commits.py
  • src/ingestion/connectors/git/bitbucket-cloud/tests/test_inaccessible_repos.py
  • src/ingestion/connectors/git/bitbucket-cloud/tests/test_reliability.py
  • src/ingestion/connectors/git/bitbucket-cloud/tests/test_request_budget.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/ingestion/connectors/git/bitbucket-cloud/tests/conftest.py
  • src/ingestion/connectors/git/bitbucket-cloud/source_bitbucket_cloud/client.py
  • src/ingestion/connectors/git/bitbucket-cloud/tests/test_inaccessible_repos.py

@aleksdotbar
aleksdotbar merged commit 81baac8 into main Jul 29, 2026
50 checks passed
@mitasovr
mitasovr deleted the fix/bitbucket-inaccessible-repos branch July 30, 2026 04:07
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