Skip to content

revert: Bitbucket OAuth 1.0 → 2.0 migration (PR #772)#773

Closed
drazisil-codecov wants to merge 11 commits intomainfrom
revert/bitbucket-oauth-v2
Closed

revert: Bitbucket OAuth 1.0 → 2.0 migration (PR #772)#773
drazisil-codecov wants to merge 11 commits intomainfrom
revert/bitbucket-oauth-v2

Conversation

@drazisil-codecov
Copy link
Copy Markdown
Contributor

@drazisil-codecov drazisil-codecov commented Mar 17, 2026

Summary

Emergency revert for PR #772 (Bitbucket OAuth 1.0 → 2.0 migration).

  • Restores bitbucket.py to OAuth 1.0 signing
  • Restores original token refresh behavior (no refresh for Bitbucket)
  • Restores original view, tests, and VCR cassettes

When to use

Only merge this if PR #772 needs to be rolled back in production. This PR will show no diff until #772 is merged to main.

Do not merge unless rolling back #772.


Note

Low Risk
Adds documentation and a new Bitbucket integration test cassette; no production code paths are modified, so behavioral risk is minimal aside from potential test fixture mismatch.

Overview
Adds a new ADR-style note (docs/adr-or-notes/owner-deletion-chunked-in-filter.md) documenting a prior fix to chunk large materialized IN (...) deletes during owner cleanup.

Adds a Bitbucket VCR cassette for test_list_repos_generator, capturing a GET /api/2.0/repositories/codecov?page=1 response to stabilize the integration test fixture.

Written by Cursor Bugbot for commit 711a3a9. This will update automatically on new commits. Configure here.

sentry bot and others added 11 commits March 16, 2026 13:50
…eover

Generate a cryptographically random state on redirect, store it in a
signed httponly cookie, and validate it matches before exchanging the
authorization code. Also updates tests for the OAuth 2.0 flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove OAuth 1.0 query params (oauth_consumer_key, oauth_token,
oauth_version) from cassette URIs to match the new Bearer token request
format.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Access tokens expire in ~2h; refresh using stored refresh_token.
Removes the no-op early returns in the token refresh callbacks for
both API and worker that were left over from OAuth 1.0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…h errors

- Bitbucket Server still uses OAuth 1.0 so restore the return None guard
  that was incorrectly removed alongside the BITBUCKET guard
- Catch TorngitClientGeneralError/5xx from refresh_token() in api() so
  an expired/revoked refresh token raises the original 401 instead of a
  confusing refresh error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Set secure=settings.SESSION_COOKIE_SECURE on _bb_oauth_state cookie
  so it isn't transmitted over plain HTTP
- Remove unused original_url param from refresh_token()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents state cookie from being sent on cross-site requests when
SameSite=None is configured (e.g. staging). Also caps cookie lifetime
at 300s to limit the window for state reuse.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract HTTP call into _send_request() helper and make the 401 token
refresh retry explicit instead of using while/continue.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reverts all changes from the Bitbucket OAuth 2.0 migration in case
rollback is needed. Restores OAuth 1.0 signing and original token
refresh behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@drazisil-codecov drazisil-codecov marked this pull request as ready for review March 17, 2026 12:59
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Unrelated documentation included in Bitbucket OAuth revert PR
    • Removed the accidentally committed owner-deletion-chunked-in-filter.md documentation file from the revert commit and amended the commit.

Create PR

Or push these changes by commenting:

@cursor push c0b7aa2b21
Preview (c0b7aa2b21)
diff --git a/apps/codecov-api/codecov_auth/tests/unit/views/test_bitbucket.py b/apps/codecov-api/codecov_auth/tests/unit/views/test_bitbucket.py
--- a/apps/codecov-api/codecov_auth/tests/unit/views/test_bitbucket.py
+++ b/apps/codecov-api/codecov_auth/tests/unit/views/test_bitbucket.py
@@ -8,16 +8,18 @@
 from codecov_auth.models import Owner
 from codecov_auth.views.bitbucket import BitbucketLoginView
 from shared.torngit.bitbucket import Bitbucket
-from shared.torngit.exceptions import (
-    TorngitClientGeneralError,
-)
+from shared.torngit.exceptions import TorngitServer5xxCodeError
+from utils.encryption import encryptor
 
 
 def test_get_bitbucket_redirect(client, settings, mocker):
-    mocked_generate = mocker.patch.object(
+    mocked_get = mocker.patch.object(
         Bitbucket,
-        "generate_redirect_url",
-        return_value="https://bitbucket.org/site/oauth2/authorize?client_id=testqmo19ebdkseoby&response_type=code&redirect_uri=http%3A%2F%2Flocalhost&state=teststate",
+        "generate_request_token",
+        return_value={
+            "oauth_token": "testy6r2of6ajkmrub",
+            "oauth_token_secret": "testzibw5q01scpl8qeeupzh8u9yu8hz",
+        },
     )
     settings.BITBUCKET_REDIRECT_URI = "http://localhost"
     settings.BITBUCKET_CLIENT_ID = "testqmo19ebdkseoby"
@@ -26,24 +28,20 @@
     res = client.get(url, SERVER_NAME="localhost:8000")
     assert res.status_code == 302
 
-    assert "_bb_oauth_state" in res.cookies
-    cookie = res.cookies["_bb_oauth_state"]
+    assert "_oauth_request_token" in res.cookies
+    cookie = res.cookies["_oauth_request_token"]
     assert cookie.value
     assert cookie.get("domain") == settings.COOKIES_DOMAIN
-    assert cookie.get("secure")
-    assert cookie.get("samesite") == settings.COOKIE_SAME_SITE
-    assert cookie.get("max-age") == 300
-    assert mocked_generate.call_count == 1
-    # state kwarg was passed through
-    _, kwargs = mocked_generate.call_args
-    assert kwargs.get("state") is not None
+    assert (
+        res.url
+        == "https://bitbucket.org/api/1.0/oauth/authenticate?oauth_token=testy6r2of6ajkmrub"
+    )
+    mocked_get.assert_called_with(settings.BITBUCKET_REDIRECT_URI)
 
 
-def test_get_bitbucket_redirect_bitbucket_error(client, settings, mocker):
-    mocker.patch.object(
-        Bitbucket,
-        "generate_redirect_url",
-        side_effect=TorngitClientGeneralError(400, {}, "bad request"),
+def test_get_bitbucket_redirect_bitbucket_unavailable(client, settings, mocker):
+    mocked_get = mocker.patch.object(
+        Bitbucket, "generate_request_token", side_effect=TorngitServer5xxCodeError()
     )
     settings.BITBUCKET_REDIRECT_URI = "http://localhost"
     settings.BITBUCKET_CLIENT_ID = "testqmo19ebdkseoby"
@@ -51,8 +49,9 @@
     url = reverse("bitbucket-login")
     res = client.get(url, SERVER_NAME="localhost:8000")
     assert res.status_code == 302
-    assert "_bb_oauth_state" not in res.cookies
+    assert "_oauth_request_token" not in res.cookies
     assert res.url == url
+    mocked_get.assert_called_with(settings.BITBUCKET_REDIRECT_URI)
 
 
 async def fake_get_authenticated_user():
@@ -111,7 +110,7 @@
         "generate_access_token",
         return_value={
             "key": "test6tl3evq7c8vuyn",
-            "secret": "testrefreshtoken",
+            "secret": "testdm61tppb5x0tam7nae3qajhcepzz",
         },
     )
     settings.BITBUCKET_REDIRECT_URI = "http://localhost"
@@ -119,14 +118,15 @@
     settings.BITBUCKET_CLIENT_SECRET = "testfi8hzehvz453qj8mhv21ca4rf83f"
     settings.CODECOV_DASHBOARD_URL = "dashboard.value"
     settings.COOKIE_SECRET = "aaaaa"
-
-    state = "test_state_value_abc123"
     url = reverse("bitbucket-login")
+    oauth_request_token = (
+        "dGVzdDZ0bDNldnE3Yzh2dXlu|dGVzdGRtNjF0cHBiNXgwdGFtN25hZTNxYWpoY2Vweno="
+    )
     client.cookies = SimpleCookie(
         {
-            "_bb_oauth_state": signing.get_cookie_signer(salt="_bb_oauth_state").sign(
-                state
-            )
+            "_oauth_request_token": signing.get_cookie_signer(
+                salt="_oauth_request_token"
+            ).sign(encryptor.encode(oauth_request_token).decode())
         }
     )
     mock_create_user_onboarding_metric = mocker.patch(
@@ -135,17 +135,17 @@
 
     res = client.get(
         url,
-        {"code": "auth_code_from_bitbucket", "state": state},
+        {"oauth_verifier": 8519288973, "oauth_token": "test1daxl4jnhegoh4"},
         SERVER_NAME="localhost:8000",
     )
     assert res.status_code == 302
     assert res.url == "dashboard.value/bb"
-    assert "_bb_oauth_state" in res.cookies
-    cookie = res.cookies["_bb_oauth_state"]
+    assert "_oauth_request_token" in res.cookies
+    cookie = res.cookies["_oauth_request_token"]
     assert cookie.value == ""
     assert cookie.get("domain") == settings.COOKIES_DOMAIN
     mocked_get.assert_called_with(
-        "auth_code_from_bitbucket", settings.BITBUCKET_REDIRECT_URI
+        "test6tl3evq7c8vuyn", "testdm61tppb5x0tam7nae3qajhcepzz", "8519288973"
     )
     owner = Owner.objects.get(username="ThiagoCodecov", service="bitbucket")
     expected_call = call(
@@ -155,8 +155,13 @@
     )
     assert mock_create_user_onboarding_metric.call_args_list == [expected_call]
 
+    assert (
+        encryptor.decode(owner.oauth_token)
+        == "test6tl3evq7c8vuyn:testdm61tppb5x0tam7nae3qajhcepzz"
+    )
 
-def test_get_bitbucket_already_token_no_state_cookie(
+
+def test_get_bitbucket_already_token_no_cookie(
     client, settings, mocker, db, mock_redis
 ):
     mocker.patch(
@@ -170,7 +175,7 @@
         "generate_access_token",
         return_value={
             "key": "test6tl3evq7c8vuyn",
-            "secret": "testrefreshtoken",
+            "secret": "testdm61tppb5x0tam7nae3qajhcepzz",
         },
     )
     settings.BITBUCKET_REDIRECT_URI = "http://localhost"
@@ -179,7 +184,7 @@
     url = reverse("bitbucket-login")
     res = client.get(
         url,
-        {"code": "auth_code_from_bitbucket", "state": "some_state"},
+        {"oauth_verifier": 8519288973, "oauth_token": "test1daxl4jnhegoh4"},
         SERVER_NAME="localhost:8000",
     )
     assert res.status_code == 302
@@ -187,38 +192,6 @@
     assert not mocked_get.called
 
 
-def test_get_bitbucket_state_mismatch(client, settings, mocker, db, mock_redis):
-    mocked_get = mocker.patch.object(
-        Bitbucket,
-        "generate_access_token",
-        return_value={
-            "key": "test6tl3evq7c8vuyn",
-            "secret": "testrefreshtoken",
-        },
-    )
-    settings.BITBUCKET_REDIRECT_URI = "http://localhost"
-    settings.BITBUCKET_CLIENT_ID = "testqmo19ebdkseoby"
-    settings.BITBUCKET_CLIENT_SECRET = "testfi8hzehvz453qj8mhv21ca4rf83f"
-    settings.COOKIE_SECRET = "aaaaa"
-
-    url = reverse("bitbucket-login")
-    client.cookies = SimpleCookie(
-        {
-            "_bb_oauth_state": signing.get_cookie_signer(salt="_bb_oauth_state").sign(
-                "legit_state"
-            )
-        }
-    )
-    res = client.get(
-        url,
-        {"code": "auth_code_from_bitbucket", "state": "attacker_injected_state"},
-        SERVER_NAME="localhost:8000",
-    )
-    assert res.status_code == 302
-    assert res.url == "/login/bitbucket"
-    assert not mocked_get.called
-
-
 class TestBitbucketLoginView(TestCase):
     def test_fetch_user_data(self):
         async def fake_list_teams():

diff --git a/apps/codecov-api/codecov_auth/views/bitbucket.py b/apps/codecov-api/codecov_auth/views/bitbucket.py
--- a/apps/codecov-api/codecov_auth/views/bitbucket.py
+++ b/apps/codecov-api/codecov_auth/views/bitbucket.py
@@ -1,5 +1,6 @@
+import base64
 import logging
-import secrets
+from urllib.parse import urlencode
 
 from asgiref.sync import async_to_sync
 from django.conf import settings
@@ -12,10 +13,8 @@
     UserOnboardingMetricsService,
 )
 from shared.torngit import Bitbucket
-from shared.torngit.exceptions import (
-    TorngitClientGeneralError,
-    TorngitServerFailureError,
-)
+from shared.torngit.exceptions import TorngitServerFailureError
+from utils.encryption import encryptor
 
 log = logging.getLogger(__name__)
 
@@ -54,19 +53,23 @@
                 "secret": settings.BITBUCKET_CLIENT_SECRET,
             }
         )
-        state = secrets.token_urlsafe(32)
-        url_to_redirect = repo_service.generate_redirect_url(
-            settings.BITBUCKET_REDIRECT_URI, state=state
+        oauth_token_pair = repo_service.generate_request_token(
+            settings.BITBUCKET_REDIRECT_URI
         )
+        oauth_token = oauth_token_pair["oauth_token"]
+        oauth_token_secret = oauth_token_pair["oauth_token_secret"]
+        url_params = urlencode({"oauth_token": oauth_token})
+        url_to_redirect = f"{Bitbucket._OAUTH_AUTHORIZE_URL}?{url_params}"
         response = redirect(url_to_redirect)
+        data = (
+            base64.b64encode(oauth_token.encode())
+            + b"|"
+            + base64.b64encode(oauth_token_secret.encode())
+        ).decode()
         response.set_signed_cookie(
-            "_bb_oauth_state",
-            state,
+            "_oauth_request_token",
+            encryptor.encode(data).decode(),
             domain=settings.COOKIES_DOMAIN,
-            httponly=True,
-            secure=settings.SESSION_COOKIE_SECURE,
-            samesite=settings.COOKIE_SAME_SITE,
-            max_age=300,
         )
         self.store_to_cookie_utm_tags(response)
         return response
@@ -78,13 +81,19 @@
                 "secret": settings.BITBUCKET_CLIENT_SECRET,
             }
         )
-        expected_state = request.get_signed_cookie("_bb_oauth_state", default=None)
-        if not expected_state or request.GET.get("state") != expected_state:
-            log.warning("Bitbucket OAuth state mismatch — possible CSRF attempt")
+        oauth_verifier = request.GET.get("oauth_verifier")
+        request_cookie = request.get_signed_cookie("_oauth_request_token", default=None)
+        if not request_cookie:
+            log.warning(
+                "Request arrived with proper url params but not the proper cookies"
+            )
             return redirect(reverse("bitbucket-login"))
-        code = request.GET.get("code")
+        request_cookie = encryptor.decode(request_cookie)
+        cookie_key, cookie_secret = [
+            base64.b64decode(i).decode() for i in request_cookie.split("|")
+        ]
         token = repo_service.generate_access_token(
-            code, settings.BITBUCKET_REDIRECT_URI
+            cookie_key, cookie_secret, oauth_verifier
         )
         user_dict = self.fetch_user_data(token)
         user = self.get_and_modify_owner(user_dict, request)
@@ -93,7 +102,7 @@
             redirection_url, user
         )
         response = redirect(redirection_url)
-        response.delete_cookie("_bb_oauth_state", domain=settings.COOKIES_DOMAIN)
+        response.delete_cookie("_oauth_request_token", domain=settings.COOKIES_DOMAIN)
         self.login_owner(user, request, response)
         log.info("User successfully logged in", extra={"ownerid": user.ownerid})
         UserOnboardingMetricsService.create_user_onboarding_metric(
@@ -106,7 +115,7 @@
             return redirect(f"{settings.CODECOV_DASHBOARD_URL}/login")
 
         try:
-            if request.GET.get("code"):
+            if request.GET.get("oauth_verifier"):
                 log.info("Logging into bitbucket after authorization")
                 return self.actual_login_step(request)
             else:
@@ -115,6 +124,3 @@
         except TorngitServerFailureError:
             log.warning("Bitbucket not available for login")
             return redirect(reverse("bitbucket-login"))
-        except TorngitClientGeneralError:
-            log.warning("Bitbucket OAuth error during login")
-            return redirect(reverse("bitbucket-login"))

diff --git a/apps/codecov-api/services/repo_providers.py b/apps/codecov-api/services/repo_providers.py
--- a/apps/codecov-api/services/repo_providers.py
+++ b/apps/codecov-api/services/repo_providers.py
@@ -42,7 +42,7 @@
     """
     if owner is None:
         return None
-    if service == Service.BITBUCKET_SERVER:
+    if service == Service.BITBUCKET or service == Service.BITBUCKET_SERVER:
         return None
 
     @sync_to_async

diff --git a/apps/codecov-api/services/tests/test_repo_providers.py b/apps/codecov-api/services/tests/test_repo_providers.py
--- a/apps/codecov-api/services/tests/test_repo_providers.py
+++ b/apps/codecov-api/services/tests/test_repo_providers.py
@@ -111,6 +111,7 @@
     "should_have_owner,service",
     [
         (False, Service.GITHUB.value),
+        (True, Service.BITBUCKET.value),
         (True, Service.BITBUCKET_SERVER.value),
     ],
 )
@@ -121,13 +122,6 @@
     assert get_token_refresh_callback(owner, service) is None
 
 
-def test_token_refresh_callback_bitbucket(db):
-    owner = OwnerFactory(service=Service.BITBUCKET.value)
-    callback = get_token_refresh_callback(owner, Service.BITBUCKET)
-    assert callback is not None
-    assert inspect.iscoroutinefunction(callback)
-
-
 GITHUB_SENTRY_APP_ID = 4321
 
 

diff --git a/apps/worker/helpers/token_refresh.py b/apps/worker/helpers/token_refresh.py
--- a/apps/worker/helpers/token_refresh.py
+++ b/apps/worker/helpers/token_refresh.py
@@ -19,7 +19,7 @@
         return None
 
     service = owner.service
-    if service == "bitbucket_server":
+    if service == "bitbucket" or service == "bitbucket_server":
         return None
 
     async def callback(new_token: dict) -> None:

diff --git a/apps/worker/services/tests/test_repository_service.py b/apps/worker/services/tests/test_repository_service.py
--- a/apps/worker/services/tests/test_repository_service.py
+++ b/apps/worker/services/tests/test_repository_service.py
@@ -224,7 +224,7 @@
     }
     assert res.data == expected_data
     assert repo.author.service == "bitbucket"
-    assert res._on_token_refresh is not None
+    assert res._on_token_refresh is None
     assert res.token == {
         "username": repo.author.username,
         "key": "testyftq3ovzkb3zmt823u3t04lkrt9w",

diff --git a/libs/shared/shared/torngit/bitbucket.py b/libs/shared/shared/torngit/bitbucket.py
--- a/libs/shared/shared/torngit/bitbucket.py
+++ b/libs/shared/shared/torngit/bitbucket.py
@@ -3,6 +3,7 @@
 import urllib.parse as urllib_parse
 
 import httpx
+from oauthlib import oauth1
 
 from shared.torngit.base import TokenType, TorngitBaseAdapter
 from shared.torngit.enums import Endpoints
@@ -23,8 +24,9 @@
 
 
 class Bitbucket(TorngitBaseAdapter):
-    _OAUTH_AUTHORIZE_URL = "https://bitbucket.org/site/oauth2/authorize"
-    _OAUTH_ACCESS_TOKEN_URL = "https://bitbucket.org/site/oauth2/access_token"
+    _OAUTH_REQUEST_TOKEN_URL = "https://bitbucket.org/api/1.0/oauth/request_token"
+    _OAUTH_ACCESS_TOKEN_URL = "https://bitbucket.org/api/1.0/oauth/access_token"
+    _OAUTH_AUTHORIZE_URL = "https://bitbucket.org/api/1.0/oauth/authenticate"
     service = "bitbucket"
     api_url = "https://bitbucket.org"
     service_url = "https://bitbucket.org"
@@ -43,22 +45,6 @@
         "compare": "{username}/{name}",
     }
 
-    async def _send_request(self, client, method, url, kwargs, log_dict):
-        try:
-            res = await client.request(method.upper(), url, **kwargs)
-            logged_body = None
-            if res.status_code >= 300 and res.text is not None:
-                logged_body = res.text
-            log.log(
-                logging.WARNING if res.status_code >= 300 else logging.INFO,
-                "Bitbucket HTTP %s",
-                res.status_code,
-                extra=dict(body=logged_body, **log_dict),
-            )
-            return res
-        except (httpx.NetworkError, httpx.TimeoutException):
-            raise TorngitServerUnreachableError("Bitbucket was not able to be reached.")
-
     async def api(
         self, client, version, method, path, json=False, body=None, token=None, **kwargs
     ):
@@ -68,19 +54,30 @@
             "User-Agent": os.getenv("USER_AGENT", "Default"),
         }
 
+        oauth_body = None
         url = url_concat(url, kwargs)
 
         if json:
             headers["Content-Type"] = "application/json"
         elif body is not None:
             headers["Content-Type"] = "application/x-www-form-urlencoded"
+            oauth_body = body
 
         token_to_use = token or self.token
-        headers["Authorization"] = f"Bearer {token_to_use['key']}"
+        oauth_client = oauth1.Client(
+            self._oauth_consumer_token()["key"],
+            client_secret=self._oauth_consumer_token()["secret"],
+            resource_owner_key=token_to_use["key"],
+            resource_owner_secret=token_to_use["secret"],
+            signature_type=oauth1.SIGNATURE_TYPE_QUERY,
+        )
+        url, headers, oauth_body = oauth_client.sign(
+            url, http_method=method, body=oauth_body, headers=headers
+        )
 
         kwargs = {
             "json": body if body is not None and json else None,
-            "data": body if body is not None and not json else None,
+            "data": oauth_body if not json else None,
             "headers": headers,
         }
         log_dict = {
@@ -90,24 +87,19 @@
             "bot": token_to_use.get("username"),
             "repo_slug": self.slug,
         }
-
-        res = await self._send_request(client, method, url, kwargs, log_dict)
-
-        if res.status_code == 401 and callable(self._on_token_refresh):
-            new_token = None
-            try:
-                new_token = await self.refresh_token(client)
-            except (TorngitClientGeneralError, TorngitServer5xxCodeError):
-                log.warning(
-                    "Bitbucket token refresh failed, raising original 401",
-                    extra=log_dict,
-                )
-            if new_token is not None:
-                headers["Authorization"] = f"Bearer {new_token['key']}"
-                kwargs["headers"] = headers
-                await self._on_token_refresh(new_token)
-                res = await self._send_request(client, method, url, kwargs, log_dict)
-
+        try:
+            res = await client.request(method.upper(), url, **kwargs)
+            logged_body = None
+            if res.status_code >= 300 and res.text is not None:
+                logged_body = res.text
+            log.log(
+                logging.WARNING if res.status_code >= 300 else logging.INFO,
+                "Bitbucket HTTP %s",
+                res.status_code,
+                extra=dict(body=logged_body, **log_dict),
+            )
+        except (httpx.NetworkError, httpx.TimeoutException):
+            raise TorngitServerUnreachableError("Bitbucket was not able to be reached.")
         if res.status_code == 599:
             raise TorngitServerUnreachableError(
                 "Bitbucket was not able to be reached, server timed out."
@@ -117,9 +109,7 @@
         elif res.status_code >= 300:
             message = f"Bitbucket API: {res.reason_phrase}"
             raise TorngitClientGeneralError(
-                res.status_code,
-                response_data={"content": res.content},
-                message=message,
+                res.status_code, response_data={"content": res.content}, message=message
             )
         if res.status_code == 204:
             return None
@@ -128,83 +118,34 @@
         else:
             return res.text
 
-    async def refresh_token(self, client):
-        """
-        Exchanges the stored refresh token for a new access + refresh token pair.
-        Returns the new token dict, or None if no refresh token is stored.
-
-        ! side effect: updates self._token
-        """
-        current_token = self.token
-        if not current_token.get("secret"):
-            log.warning("Trying to refresh Bitbucket token with no refresh_token saved")
-            return None
-
-        res = await client.request(
-            "POST",
-            self._OAUTH_ACCESS_TOKEN_URL,
-            data={
-                "grant_type": "refresh_token",
-                "refresh_token": current_token["secret"],
-            },
-            auth=(self._oauth["key"], self._oauth["secret"]),
+    def generate_request_token(self, redirect_url):
+        client = oauth1.Client(
+            self._oauth["key"],
+            client_secret=self._oauth["secret"],
+            callback_uri=redirect_url,
         )
-        if res.status_code >= 500:
-            raise TorngitServer5xxCodeError("Bitbucket is having 5xx issues")
-        if res.status_code >= 400:
-            raise TorngitClientGeneralError(
-                res.status_code,
-                response_data={"content": res.content},
-                message="Bitbucket token refresh failed",
-            )
-        data = res.json()
-        new_token = {
-            "key": data["access_token"],
-            "secret": data.get("refresh_token", ""),
-        }
-        self.set_token(new_token)
-        return new_token
+        uri, headers, body = client.sign(self._OAUTH_REQUEST_TOKEN_URL)
+        r = httpx.get(uri, headers=headers)
+        oauth_token = urllib_parse.parse_qs(r.text)["oauth_token"][0]
+        oauth_token_secret = urllib_parse.parse_qs(r.text)["oauth_token_secret"][0]
+        return {"oauth_token": oauth_token, "oauth_token_secret": oauth_token_secret}
 
-    def generate_redirect_url(self, redirect_url, state=None):
-        """
-        Returns the Bitbucket OAuth 2.0 authorization URL to redirect the user to.
-        """
-        params: dict = {
-            "client_id": self._oauth["key"],
-            "response_type": "code",
-            "redirect_uri": redirect_url,
-        }
-        if state is not None:
-            params["state"] = state
-        return f"{self._OAUTH_AUTHORIZE_URL}?{urllib_parse.urlencode(params)}"
-
-    def generate_access_token(self, code, redirect_url):
-        """
-        Exchanges an OAuth 2.0 authorization code for an access token.
-        Returns a dict with 'key' (access token) and 'secret' (refresh token).
-        """
-        r = httpx.post(
-            self._OAUTH_ACCESS_TOKEN_URL,
-            data={
-                "grant_type": "authorization_code",
-                "code": code,
-                "redirect_uri": redirect_url,
-            },
-            auth=(self._oauth["key"], self._oauth["secret"]),
+    def generate_access_token(
+        self, resource_owner_key, resource_owner_secret, verifier
+    ):
+        client = oauth1.Client(
+            self._oauth["key"],
+            client_secret=self._oauth["secret"],
+            resource_owner_key=resource_owner_key,
+            resource_owner_secret=resource_owner_secret,
+            verifier=verifier,
         )
-        if r.status_code >= 500:
-            raise TorngitServer5xxCodeError("Bitbucket is having 5xx issues")
-        elif r.status_code >= 400:
-            message = f"Bitbucket OAuth2: {r.reason_phrase}"
-            raise TorngitClientGeneralError(
-                r.status_code,
-                response_data={"content": r.content},
-                message=message,
-            )
-        data = r.json()
+        uri, headers, body = client.sign(self._OAUTH_ACCESS_TOKEN_URL)
+        r = httpx.get(uri, headers=headers)
+        resp_args = urllib_parse.parse_qs(r.text)
         return {
-            "key": data["access_token"],
-            "secret": data.get("refresh_token", ""),
+            "key": resp_args["oauth_token"][0],
+            "secret": resp_args["oauth_token_secret"][0],
         }
 
     async def get_authenticated_user(self):

diff --git a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_delete_comment.yaml b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_delete_comment.yaml
--- a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_delete_comment.yaml
+++ b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_delete_comment.yaml
@@ -7,7 +7,7 @@
       User-Agent:
       - Default
     method: DELETE
-    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/pullrequests/1/comments/107383471
+    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/pullrequests/1/comments/107383471?oauth_version=1.0&oauth_token=testss3hxhcfqf1h6g&oauth_nonce=0441f5ae229a4884801e97004003b6a8&oauth_timestamp=1561670312&oauth_signature=bRwiaWHiDGhoJQQuFG9%2BNwbERAg%3D&oauth_consumer_key=arubajamaicaohiwan&oauth_signature_method=HMAC-SHA1
   response:
     content: ''
     headers:

diff --git a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_delete_comment_not_found.yaml b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_delete_comment_not_found.yaml
--- a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_delete_comment_not_found.yaml
+++ b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_delete_comment_not_found.yaml
@@ -7,7 +7,7 @@
       User-Agent:
       - Default
     method: DELETE
-    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/pullrequests/1/comments/113977999
+    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/pullrequests/1/comments/113977999?oauth_version=1.0&oauth_token=testss3hxhcfqf1h6g&oauth_nonce=331c6e3852554f5497b04e564dc673c2&oauth_timestamp=1561668838&oauth_signature=Z9fmZ6u%2FG%2Bimv4BHN1PNy7v3WkE%3D&oauth_consumer_key=arubajamaicaohiwan&oauth_signature_method=HMAC-SHA1
   response:
     content: '{"type": "error", "error": {"message": "113977999"}}'
     headers:

diff --git a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_delete_webhook.yaml b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_delete_webhook.yaml
--- a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_delete_webhook.yaml
+++ b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_delete_webhook.yaml
@@ -7,7 +7,7 @@
       User-Agent:
       - Default
     method: DELETE
-    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/hooks/4742f092-8397-4677-8876-5e9a06f10f98
+    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/hooks/4742f092-8397-4677-8876-5e9a06f10f98?oauth_consumer_key=arubajamaicaohiwan&oauth_token=testss3hxhcfqf1h6g&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1541602115&oauth_nonce=ab985dba77534c24929b8d013d2cb876&oauth_version=1.0&oauth_signature=BCk5R%2Bbn4a3MUcDiyTQ3HF2X%2BW8%3D
   response:
     content: ''
     headers:

diff --git a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_delete_webhook_not_found.yaml b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_delete_webhook_not_found.yaml
--- a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_delete_webhook_not_found.yaml
+++ b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_delete_webhook_not_found.yaml
@@ -7,7 +7,7 @@
       User-Agent:
       - Default
     method: DELETE
-    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/hooks/4742f011-8397-aa77-8876-5e9a06f10f98
+    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/hooks/4742f011-8397-aa77-8876-5e9a06f10f98?oauth_consumer_key=arubajamaicaohiwan&oauth_token=testss3hxhcfqf1h6g&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1541602198&oauth_nonce=fc8ef877776d441bab406f7a47396073&oauth_version=1.0&oauth_signature=A%2F46Dp0fgbZI9lkjovmcu4LfGiw%3D
   response:
     content: '{"type": "error", "error": {"message": "example-python is not a valid
       hook"}}'

diff --git a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_edit_comment.yaml b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_edit_comment.yaml
--- a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_edit_comment.yaml
+++ b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_edit_comment.yaml
@@ -9,7 +9,7 @@
       User-Agent:
       - Default
     method: PUT
-    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/pullrequests/1/comments/114320127
+    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/pullrequests/1/comments/114320127?oauth_consumer_key=arubajamaicaohiwan&oauth_token=testss3hxhcfqf1h6g&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1566631440&oauth_nonce=5aa764bebf044e64beb9d9df2912cc08&oauth_version=1.0&oauth_signature=2mI6UpQrKaN8s0sL9N%2F9ItZaF7o%3D
   response:
     content: '{"links": {"self": {"href": "https://bitbucket.org/!api/2.0/repositories/ThiagoCodecov/example-python/pullrequests/1/comments/114320127"},
       "html": {"href": "https://bitbucket.org/ThiagoCodecov/example-python/pull-requests/1/_/diff#comment-114320127"}},

diff --git a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_edit_comment_not_found.yaml b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_edit_comment_not_found.yaml
--- a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_edit_comment_not_found.yaml
+++ b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_edit_comment_not_found.yaml
@@ -9,7 +9,7 @@
       User-Agent:
       - Default
     method: PUT
-    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/pullrequests/1/comments/113979999
+    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/pullrequests/1/comments/113979999?oauth_consumer_key=arubajamaicaohiwan&oauth_token=testss3hxhcfqf1h6g&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1566631150&oauth_nonce=5aeac543acea436d9f0594d750a98dd3&oauth_version=1.0&oauth_signature=16DIGrfTGyeo8k90tViVI%2BcqBPw%3D
   response:
     content: '{"type": "error", "error": {"message": "113979999"}}'
     headers:

diff --git a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_edit_webhook.yaml b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_edit_webhook.yaml
--- a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_edit_webhook.yaml
+++ b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_edit_webhook.yaml
@@ -10,7 +10,7 @@
       User-Agent:
       - Default
     method: PUT
-    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/hooks/4742f092-8397-4677-8876-5e9a06f10f98
+    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/hooks/4742f092-8397-4677-8876-5e9a06f10f98?oauth_consumer_key=arubajamaicaohiwan&oauth_token=testss3hxhcfqf1h6g&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1541602041&oauth_nonce=ddb0335319f246d98933af48a59371e5&oauth_version=1.0&oauth_signature=widTrBs%2BlgaUlwKjY2YTvYMpWlc%3D
   response:
     content: '{"read_only": null, "description": "new_name", "links": {"self": {"href":
       "https://bitbucket.org/!api/2.0/repositories/ThiagoCodecov/example-python/hooks/%7B4742f092-8397-4677-8876-5e9a06f10f98%7D"}},

diff --git a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_find_pull_request_nothing_found.yaml b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_find_pull_request_nothing_found.yaml
--- a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_find_pull_request_nothing_found.yaml
+++ b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_find_pull_request_nothing_found.yaml
@@ -7,7 +7,7 @@
       User-Agent:
       - Default
     method: GET
-    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/pullrequests?state=OPEN&page=1
+    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/pullrequests?state=OPEN&page=1&oauth_consumer_key=arubajamaicaohiwan&oauth_token=testss3hxhcfqf1h6g&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1541516646&oauth_nonce=15af96bfd6e546acaec32b3dac09fe42&oauth_version=1.0&oauth_signature=4E9qt33md6cz0B%2BnnL62C2hs7jA%3D
   response:
     content: '{"pagelen": 10, "values": [], "page": 1, "size": 0}'
     headers:

diff --git a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_ancestors_tree.yaml b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_ancestors_tree.yaml
--- a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_ancestors_tree.yaml
+++ b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_ancestors_tree.yaml
@@ -7,7 +7,7 @@
       User-Agent:
       - Default
     method: GET
-    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/commits?include=6ae5f17
+    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/commits?include=6ae5f17&oauth_consumer_key=arubajamaicaohiwan&oauth_token=testss3hxhcfqf1h6g&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1568980339&oauth_nonce=1dcb7a2eedd544b395184b18231388a8&oauth_version=1.0&oauth_signature=XdUUtgGHNy2XcsbYlW%2Ftq3Od4qo%3D
   response:
     content: "{\"pagelen\": 30, \"values\": [{\"rendered\": {\"message\": {\"raw\"\
       : \"Update README.rst\", \"markup\": \"markdown\", \"html\": \"<p>Update README.rst</p>\"\

diff --git a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_authenticated.yaml b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_authenticated.yaml
--- a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_authenticated.yaml
+++ b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_authenticated.yaml
@@ -13,7 +13,7 @@
       user-agent:
       - Default
     method: GET
-    uri: https://bitbucket.org/api/2.0/user/permissions/repositories?q=repository.full_name%3D%22ThiagoCodecov%2Fexample-python%22+AND+%28permission%3D%22admin%22+OR+permission%3D%22write%22%29
+    uri: https://bitbucket.org/api/2.0/user/permissions/repositories?q=repository.full_name%3D%22ThiagoCodecov%2Fexample-python%22+AND+%28permission%3D%22admin%22+OR+permission%3D%22write%22%29&oauth_consumer_key=arubajamaicaohiwan&oauth_token=testss3hxhcfqf1h6g&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1617377623&oauth_nonce=ac91652521bc4867af3ec36b448a016c&oauth_version=1.0&oauth_signature=BQrCWoBGobnzvY0aRJ51OBJqyPk%3D
   response:
     content: '{"pagelen": 10, "values": [{"type": "repository_permission", "user":
       {"display_name": "Thiago Ramos", "uuid": "{9a01f37b-b1b2-40c5-8c5e-1a39f4b5e645}",

diff --git a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_authenticated_no_edit_permission.yaml b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_authenticated_no_edit_permission.yaml
--- a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_authenticated_no_edit_permission.yaml
+++ b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_authenticated_no_edit_permission.yaml
@@ -13,7 +13,7 @@
       user-agent:
       - Default
     method: GET
-    uri: https://bitbucket.org/api/2.0/user/permissions/repositories?q=repository.full_name%3D%22atlassian%2Fstash-example-plugin%22+AND+%28permission%3D%22admin%22+OR+permission%3D%22write%22%29
+    uri: https://bitbucket.org/api/2.0/user/permissions/repositories?q=repository.full_name%3D%22atlassian%2Fstash-example-plugin%22+AND+%28permission%3D%22admin%22+OR+permission%3D%22write%22%29&oauth_consumer_key=arubajamaicaohiwan&oauth_token=testss3hxhcfqf1h6g&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1617377940&oauth_nonce=54b366ce70c843e38c6f5e2d08b7d25b&oauth_version=1.0&oauth_signature=lihQwQ9cT1CJGybvYG1NAYvWuYw%3D
   response:
     content: '{"pagelen": 10, "values": [], "page": 1}'
     headers:

diff --git a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_branches.yaml b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_branches.yaml
--- a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_branches.yaml
+++ b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_branches.yaml
@@ -7,7 +7,7 @@
       User-Agent:
       - Default
     method: GET
-    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/refs/branches?pagelen=100
+    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/refs/branches?oauth_nonce=68196bfc35d5473cba07ec92748644c1&oauth_timestamp=1561674319&oauth_consumer_key=arubajamaicaohiwan&oauth_signature_method=HMAC-SHA1&oauth_version=1.0&oauth_token=testss3hxhcfqf1h6g&oauth_signature=7%2BIvkm9uQV%2FozMt2tOdrE4Hf5dY%3D&pagelen=100
   response:
     content: '{"pagelen": 100, "values": [{"name": "f/new-branch", "links": {"commits":
       {"href": "https://bitbucket.org/!api/2.0/repositories/ThiagoCodecov/example-python/commits/f/new-branch"},

diff --git a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_commit.yaml b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_commit.yaml
--- a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_commit.yaml
+++ b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_commit.yaml
@@ -7,7 +7,7 @@
       User-Agent:
       - Default
     method: GET
-    uri: https://bitbucket.org/api/2.0/repositories/codecov/private/commit/6a45b83
+    uri: https://bitbucket.org/api/2.0/repositories/codecov/private/commit/6a45b83?oauth_consumer_key=arubajamaicaohiwan&oauth_token=waydowntokokomo&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1575576386&oauth_nonce=7487fd483bfd4daaa38904b8d9feca99&oauth_version=1.0&oauth_signature=QBngQxqDm8r4CVe5UnZK6KUEF60%3D
   response:
     content: '{"rendered": {"message": {"raw": "wip\n", "markup": "markdown", "html":
       "<p>wip</p>", "type": "rendered"}}, "hash": "6a45b838ae4fe22953c93aa17cc41b4b4216eb93",
@@ -94,7 +94,7 @@
       User-Agent:
       - Default
     method: GET
-    uri: https://bitbucket.org/api/2.0/users/stevepeak
+    uri: https://bitbucket.org/api/2.0/users/stevepeak?oauth_consumer_key=arubajamaicaohiwan&oauth_token=waydowntokokomo&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1575576388&oauth_nonce=9b743f0720d74655a3b083aa8d756bdf&oauth_version=1.0&oauth_signature=o69UGzzNxIMf6CZ%2B1BdnMIclt5I%3D
   response:
     content: '{"display_name": "Steve Peak", "uuid": "{test6y9pl15lzivhmkgsk67k10x53n04i85o}",
       "links": {"hooks": {"href": "https://bitbucket.org/!api/2.0/users/%7Btest6y9pl15lzivhmkgsk67k10x53n04i85o%7D/hooks"},

diff --git a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_commit_diff.yaml b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_commit_diff.yaml
--- a/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_commit_diff.yaml
+++ b/libs/shared/tests/integration/cassetes/test_bitbucket/TestBitbucketTestCase/test_get_commit_diff.yaml
@@ -7,7 +7,7 @@
       User-Agent:
       - Default
     method: GET
-    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/diff/3017d53
+    uri: https://bitbucket.org/api/2.0/repositories/ThiagoCodecov/example-python/diff/3017d53?oauth_consumer_key=arubajamaicaohiwan&oauth_token=testss3hxhcfqf1h6g&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1541599673&oauth_nonce=7f08d97c0f8f4826b4e9c79f9969f788&oauth_version=1.0&oauth_signature=NQpb%2B4E%2BtSRDp9XufM9d6ha1QVk%3D
   response:
     content: 'diff --git a/awesome/code_fib.py b/awesome/code_fib.py
 

... diff truncated: showing 800 of 1492 lines

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

- More database round-trips per cleanup run — 50,000 rows that were one DELETE are now five. For very large owners this makes cleanup slower.
- Deletes within a single model are no longer atomic — if the process crashes between chunks, some rows will be deleted and others won't. This was already a property of the broader cleanup design, not a new regression.

The trade-off is acceptable: cleanup is a background job where slower-but-reliable is preferable to fast-but-crashes.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unrelated documentation included in Bitbucket OAuth revert PR

Low Severity

The file docs/adr-or-notes/owner-deletion-chunked-in-filter.md documents PR #731 (chunked IN filter for owner deletion), which is entirely unrelated to the Bitbucket OAuth 1.0 → 2.0 revert (PR #772). This documentation file appears to have been accidentally included in the revert PR and would be confusing in the commit history when trying to understand the revert.

Fix in Cursor Fix in Web

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Mar 17, 2026

Merging this PR will not alter performance

✅ 9 untouched benchmarks


Comparing revert/bitbucket-oauth-v2 (711a3a9) with main (8eb0409)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (d5d05f9) during the generation of this report, so 8eb0409 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@sentry
Copy link
Copy Markdown
Contributor

sentry bot commented Mar 17, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.27%. Comparing base (d5d05f9) to head (711a3a9).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #773   +/-   ##
=======================================
  Coverage   92.27%   92.27%           
=======================================
  Files        1305     1305           
  Lines       47938    47938           
  Branches     1628     1628           
=======================================
  Hits        44233    44233           
  Misses       3396     3396           
  Partials      309      309           
Flag Coverage Δ
apiunit 96.36% <ø> (ø)
sharedintegration 36.99% <ø> (ø)
sharedunit 84.91% <ø> (ø)
workerintegration 58.56% <ø> (ø)
workerunit 90.40% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@codecov-notifications
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

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.

2 participants