Bitbucket

July 5, 2026 ยท View on GitHub

A legacy Git repo hosting provider with the worst CI/CD of the major 4 Git hosting providers.

SSH Keys

https://bitbucket.org/account/settings/ssh-keys/

App Passwords

Deprecated in favour of API Tokens.

https://developer.atlassian.com/cloud/bitbucket/changelog/#CHANGE-3222

https://bitbucket.org/account/settings/app-passwords/

API Tokens

Create an API token here with the Bitbucket scope and ensure you tick all the Read and Write permissions:

https://id.atlassian.com/manage-profile/security/api-tokens

and then copy, save it and export it:

export BITBUCKET_TOKEN=...

https://support.atlassian.com/bitbucket-cloud/docs/using-api-tokens/

Git Clone over HTTPS using App Password or API Token

Cloning with HTTPS then becomes:

git clone "https://$BITBUCKET_USER:$BITBUCKET_TOKEN@bitbucket.org/$ORG/$REPO.git"

or better using a credential helper...

Credential Helper

Taken from my .gitconfig:

[credential "https://bitbucket.org"]
    helper = "!f() { sleep 1; echo \"password=${BITBUCKET_TOKEN}\"; }; f"
git clone "https://bitbucket.org/$ORG/$REPO.git"

If you get a 401 or 403 authentication or authorization error...

Check which credential helper you're using:

git config --list --show-origin | grep credential.helper
file:/opt/homebrew/etc/gitconfig        credential.helper=osxkeychain

But the URL specific helper should take precedence:

git config --get-urlmatch credential.helper https://bitbucket.org

Output, we see our function:

!f() { sleep 1; echo "password=${BITBUCKET_TOKEN}"; }; f

Check what Git credentials are actually being used:

printf 'protocol=https\nhost=bitbucket.org\n\n' | git credential fill

General Git debug output:

export GIT_CURL_VERBOSE=1

Or in this case this was more useful:

GIT_TRACE=1 GIT_TRACE_CREDENTIALS=1 git push bitbucket main

Output:

21:10:50.086154 git.c:476               trace: built-in: git push bitbucket main
...
21:10:50.473456 run-command.c:673       trace: run_command: 'git credential-osxkeychain get'
21:10:50.474767 run-command.c:765       trace: start_command: /bin/sh -c 'git credential-osxkeychain get' 'git credential-osxkeychain get'
21:10:50.490893 git.c:775               trace: exec: git-credential-osxkeychain get
21:10:50.491732 run-command.c:673       trace: run_command: git-credential-osxkeychain get

This is the problem, it's still using the osxkeychain with an old credential instead of the credential helper.

If the osxkeychain credential is an old one then you should probably open KeyChain Access, search for bitbucket and remove it so that it is not getting picked up first:

open -a 'Keychain Access'

The trace shows that the credential helper doesn't call the function unless you put the username in the URL, even though the credential helper URL doesn't contain the username as a specific requirement match.

So if you are getting the 403 error, change the URL to this:

https://$BITBUCKET_USER@bitbucket.org/...

You should then see an output like this:

...
21:19:29.447332 run-command.c:673       trace: run_command: 'git credential-osxkeychain get'
21:19:29.448723 run-command.c:765       trace: start_command: /bin/sh -c 'git credential-osxkeychain get' 'git credential-osxkeychain get'
21:19:29.466320 git.c:775               trace: exec: git-credential-osxkeychain get
21:19:29.467065 run-command.c:673       trace: run_command: git-credential-osxkeychain get
21:19:29.467099 run-command.c:765       trace: start_command: /opt/homebrew/opt/git/libexec/git-core/git-credential-osxkeychain get
21:19:29.759814 run-command.c:673       trace: run_command: 'git credential-osxkeychain store'
21:19:29.759936 run-command.c:765       trace: start_command: /bin/sh -c 'git credential-osxkeychain store' 'git credential-osxkeychain store'
21:19:29.778023 git.c:775               trace: exec: git-credential-osxkeychain store
21:19:29.778740 run-command.c:673       trace: run_command: git-credential-osxkeychain store
21:19:29.778756 run-command.c:765       trace: start_command: /opt/homebrew/opt/git/libexec/git-core/git-credential-osxkeychain store
21:19:29.783479 run-command.c:673       trace: run_command: 'f() { sleep 1; echo "password=${BITBUCKET_TOKEN}"; }; f store'
21:19:29.783549 run-command.c:765       trace: start_command: /bin/sh -c 'f() { sleep 1; echo "password=${BITBUCKET_TOKEN}"; }; f store' 'f() { sleep 1; echo "password=${BITBUCKET_TOKEN}"; }; f store'
...

Notice the last two lines which show it's now using the function after adding the username into the URL.

Putting the username in credential helper doesn't work as the helper doesn't get called at all judging by the traces, so this doesn't work on Bitbucket even though it does on other sites like GitHub, GitLab and Azure DevOps:

[credential "https://bitbucket.org"]
    helper = "!f() { sleep 1; echo \"username=${BITBUCKET_USER}\"; echo \"password=${BITBUCKET_TOKEN}\"; }; f"

Doesn't work - your still need to put the $BITBUCKET_USER@ in the git remote URL.