-
Type:
Bug
-
Resolution: Fixed
-
Priority:
Major
-
Component/s: bitbucket-branch-source-plugin
-
None
-
937.3.3
Summary
When building Pull Requests from forks on Bitbucket Cloud, the upstream (target repo) fetch fails with Authentication failed. The root cause is that GitClientAuthenticatorExtension only applies the transformed credential (with x-bitbucket-api-token-auth username) to the primary remote URL, but not to additional remotes (upstream). The upstream remote falls back to the raw Jenkins credential which uses the original username (e.g., email), which Bitbucket Cloud rejects for API token authentication.
Affected Component
GitClientAuthenticatorExtension.decorate() in bitbucket-branch-source-plugin
Environment
- Bitbucket Cloud (bitbucket.org)
- Bitbucket Branch Source plugin version 937.3.2
- Git plugin with git-client 6.x
- Jenkins credential type: Username with Password (email + API token)
- Organization Folder or Multibranch Pipeline
Steps to Reproduce
- Set up a Bitbucket Cloud organization folder in Jenkins
- Configure scan credentials as "Username with Password" where username is the user's email and password is a Bitbucket API token
- Create a fork of a repository within the same workspace
- Create a branch in the fork and open a Pull Request targeting the main branch of the original repository
- Observe the PR build triggered by the organization folder
Expected Behavior
Both the fork (origin) and the target repo (upstream) fetches authenticate successfully using the configured credential.
Actual Behavior
- The fork fetch succeeds — the credential is transformed via BitbucketUserAPITokenAuthenticator.getCredentialsForSCM() to use x-bitbucket-api-token-auth as username
- The upstream fetch fails with:
stderr: remote: You may not have access to this repository or it no longer exists in this workspace.
fatal: Authentication failed for 'https://bitbucket.org/<workspace>/<repo>.git/'
Log Evidence
Fetching upstream changes from https://bitbucket.org/<workspace>/<fork-repo>.git
using GIT_ASKPASS to set credentials User API token for <cred-id> ← transformed credential
> git fetch ... https://bitbucket.org/<workspace>/<fork-repo>.git ... ← SUCCESS
Fetching upstream changes from https://bitbucket.org/<workspace>/<target-repo>.git
using GIT_ASKPASS to set credentials <cred-id> ← raw credential (no transformation)
> git fetch ... https://bitbucket.org/<workspace>/<target-repo>.git ... ← FAILS
Note the different descriptions logged: "User API token for X" (transformed) vs just "X" (raw).
Root Cause Analysis
Flow for primary remote (works):
- BitbucketSCMSource.build() creates GitClientAuthenticatorExtension with the primary remote URL
- During checkout, GitSCM.createClient() adds raw credentials for all remotes
- GitClientAuthenticatorExtension.decorate() then overwrites the primary URL's credential with the transformed one (username = x-bitbucket-api-token-auth)
- Fetch succeeds
Flow for upstream remote (fails):
- GitSCM.createClient() adds raw credentials (username = email) for the upstream URL
- GitClientAuthenticatorExtension.decorate() does NOT touch the upstream URL — it only knows about the primary URL
- Fetch uses email as username → Bitbucket Cloud rejects it
Relevant code:
GitClientAuthenticatorExtension.decorate() — only applies to single URL:
public GitClient decorate(GitSCM scm, GitClient git) throws GitException {
if (credentialsId != null) {
BitbucketAuthenticator authenticator = authenticator();
credentials = authenticator.getCredentialsForSCM();
}
if (credentials != null) {
git.addCredentials(url, credentials); // only the primary remote URL
}
return git;
}
BitbucketSCMSource.build() — only passes primary URL:
return scmBuilder
.withExtension(new GitClientAuthenticatorExtension(checkoutURL, serverUrl, scmOwner, checkoutCredentialsId))
.build();
BitbucketGitSCMBuilder.withPullRequestRemote() — adds upstream as additional remote:
withAdditionalRemote(upstreamRemoteName, getCloneLink(primaryCloneLinks), refSpec);
This upstream URL never gets the transformed credential.
Proposed Fix
Option A: Modify GitClientAuthenticatorExtension.decorate() to apply credentials to all remotes
@Override
public GitClient decorate(GitSCM scm, GitClient git) throws GitException {
StandardUsernameCredentials credentials = this.credentials;
if (credentialsId != null) {
BitbucketAuthenticator authenticator = authenticator();
if (authenticator == null) {
throw new IllegalStateException(...);
}
credentials = authenticator.getCredentialsForSCM();
}
if (credentials != null) {
// Apply to all configured remotes, not just the primary
for (UserRemoteConfig uc : scm.getUserRemoteConfigs()) {
git.addCredentials(uc.getUrl(), credentials);
}
}
return git;
}
Option B: Pass all remote URLs to GitClientAuthenticatorExtension
Change BitbucketSCMSource.build() to collect all remote URLs (primary + additional) and pass them to the extension, then have decorate() iterate over all of them.
Option C: Minimal change in BitbucketGitSCMBuilder
In withPullRequestRemote(), after calling withAdditionalRemote(...), also register a second GitClientAuthenticatorExtension for the upstream URL. This may require allowing multiple instances of the same extension type.
Recommended: Option A
Option A is the simplest fix with the least API changes. The transformed credential (using x-bitbucket-api-token-auth) is valid for all Bitbucket Cloud repos accessible by the API token, so applying it to all remotes is safe and correct.
Workaround
None available. Changing the credential username to x-bitbucket-api-token-auth fixes the upstream fetch but breaks the Bitbucket API calls (repo scanning, PR discovery) that require the email as username. Users cannot work around this without a plugin fix.
The only alternative is to avoid fork-based PRs entirely and use branches within the same repository.
- links to