Skip to main content
Logo
Overview

Bitbucket App Passwords Die July 28, 2026: Token Migration

July 11, 2026
8 min read

If your git push to Bitbucket started failing with a 410 last Tuesday and then mysteriously worked again an hour later, you weren’t imagining it. Atlassian is running escalating brownouts on app passwords, and the windows get longer every week until July 28, when app passwords stop working permanently.

You have about two weeks. The migration itself is maybe thirty minutes of work per system. The problem is that most teams don’t know how many systems they have — the app password buried in a Jenkins credential store from 2021, the one in a composer.json auth config, the one a contractor put in a Terraform variable. Those are the ones that page you.

I want to walk through what actually breaks, what replaces what, and the one genuinely confusing part of the new scheme that has tripped up basically everyone, including the Composer maintainers.

The timeline, and what a failure looks like

App passwords have been on death row for a while. New ones stopped being creatable on September 9, 2025 — if you’ve tried to make one recently and couldn’t find the button, that’s why.

The brownouts started June 9, 2026 and escalate weekly at 00:00, 06:00, 12:00, and 18:00 UTC:

WeekBrownout duration
Jun 9–1515 minutes
Jun 16–2230 minutes
Jun 23–291 hour
Jun 30–Jul 62 hours
Jul 7–133 hours
Jul 14–204 hours
Jul 21–275 hours

July 28, 2026 is the hard removal. There’s no extension, no enterprise carve-out, no support ticket that buys you another quarter.

The failure modes are distinct, which is actually helpful for grepping your logs:

  • HTTP 401 on REST API calls
  • HTTP 410 Gone on Git-over-HTTPS

That 410 is the useful signal. A 410 from bitbucket.org means exactly one thing — a credential that no longer exists. If you’re running any kind of log aggregation, search for it now. During this week’s 3-hour windows you’ll catch every system still on an app password without having to audit anything by hand. That’s a much cheaper discovery method than reading config files.

Four credential types, and which one you actually want

Atlassian’s docs list several replacements and don’t do a great job saying which is for what. Here’s the short version.

API tokens are the direct replacement for app passwords. They’re created at the Atlassian account level (id.atlassian.com, not the Bitbucket settings page — this alone confuses people), they carry granular scopes, and they support expiry. This is what you want for anything tied to a human user: your laptop, your local Git remotes, a personal script.

Repository access tokens are scoped to exactly one repo and belong to the repo, not a person. This is what CI should use. A Jenkins job that builds one service does not need a credential that can read every repo in your workspace, and it definitely shouldn’t break when the engineer who created it leaves.

Workspace and project access tokens widen that to a whole workspace or project. Useful for a mirroring job or an org-wide scanner. One catch worth knowing before you plan around them: workspace and project access tokens are Premium-only. If you’re on Standard, this option doesn’t exist for you, and the fallback is a repository access token per repo.

OAuth 2.0 is for apps acting on behalf of other users. If you’re not building an integration that other people install, skip it.

The rule of thumb I’d use: if a person is authenticating, API token. If a machine is authenticating, repository access token. Machine credentials tied to a human account are how you end up with a build pipeline that dies three weeks after someone’s offboarding.

The username thing that breaks everything

This is the part worth reading twice, because it’s the single biggest source of “I migrated and it still doesn’t work” threads.

API tokens need a different identifier depending on whether you’re talking to Git or the REST API.

For Git over HTTPS, use the literal static string x-bitbucket-api-token-auth as the username:

git clone https://x-bitbucket-api-token-auth:$BB_TOKEN@bitbucket.org/myworkspace/myrepo.git

For the REST API, use your Atlassian account email:

curl --user "me@example.com:$BB_TOKEN" \
  https://api.bitbucket.org/2.0/repositories/myworkspace/myrepo

Same token. Different username. Email for the API, magic string for Git.

This is not a documentation nitpick — it’s a real architectural wart, and it’s why Composer’s maintainers pushed back so hard. Composer stores one credential pair and uses it for both cloning and API metadata lookups, so a scheme that demands two different usernames for one token doesn’t cleanly fit the existing config shape. If you use Composer with private Bitbucket repos, don’t assume your current auth block works. Check the Composer issue tracker for the current state before you cut over.

Your app password, for what it’s worth, worked with your plain Bitbucket username everywhere. So if you do a naive find-and-replace of the secret value and leave the username alone, you get a 401, and you’ll spend an hour convinced the token is bad. The token is fine. The username is wrong.

Scopes: stop copying admin everywhere

App passwords had coarse checkboxes. API tokens have properly granular scopes, and this is a genuine improvement — take the ten minutes to use it rather than granting everything out of habit.

The names are verbose but predictable:

read:repository:bitbucket # clone, browse files
write:repository:bitbucket # push
admin:repository:bitbucket # branch restrictions, default reviewers
delete:repository:bitbucket # deletion
read:pullrequest:bitbucket # view, comment
write:pullrequest:bitbucket # create, approve, decline, merge
read:pipeline:bitbucket # pipeline info and logs
write:pipeline:bitbucket # trigger runs, upload test results
read:webhook:bitbucket
write:webhook:bitbucket
delete:webhook:bitbucket

There are more (issues, snippets, SSH keys, GPG keys, permissions), all following the same verb:noun:bitbucket shape.

What most use cases actually need:

  • Read-only CI build: read:repository:bitbucket. That’s it. A build that only clones and compiles needs nothing else.
  • CI that pushes tags or updates PR status: add write:repository:bitbucket and write:pullrequest:bitbucket.
  • Bot that comments on PRs: read:repository:bitbucket + write:pullrequest:bitbucket. It does not need write on the repo.
  • Webhook management script: the three webhook scopes, nothing more.
  • Your laptop: read:repository:bitbucket + write:repository:bitbucket + write:pullrequest:bitbucket covers day-to-day work.

The temptation under deadline pressure is to check every box, ship it, and promise to tighten it later. Later never comes, and now you’ve turned a two-week deadline into a permanent over-privileged credential. A read-only build token costs the same thirty seconds to create as an admin one.

Migrating your local Git remotes

If you embedded an app password directly in a remote URL — and a lot of people did, because it was the path of least resistance — fix that first:

git remote -v   # if you see a secret here, that secret is in your shell history

Rewriting the remote to a clean URL and letting a credential helper hold the token is strictly better:

git remote set-url origin https://bitbucket.org/myworkspace/myrepo.git

Then wire up the helper for your platform.

macOS Keychain:

git config --global credential.helper osxkeychain

Delete the stale app password entry first, or the helper will keep handing Git a dead credential and you’ll get 410s forever:

git credential-osxkeychain erase <<EOF
protocol=https
host=bitbucket.org
EOF

Next git fetch prompts you. Username is x-bitbucket-api-token-auth, password is the token.

Linux, git-credential-store (plaintext file — acceptable on a personal machine, not on a shared build box):

git config --global credential.helper store
# ~/.git-credentials will contain:
# https://x-bitbucket-api-token-auth:TOKEN@bitbucket.org

Check whether ~/.git-credentials already has a Bitbucket line and delete it before re-authenticating. Same failure mode as Keychain.

Windows: Credential Manager caches per-host. cmdkey /list to find it, delete the bitbucket.org entry, re-auth.

Honestly, if you’re only dealing with your own machine, SSH keys sidestep this entire category of problem. App password deprecation doesn’t touch SSH at all. The reason to bother with HTTPS tokens is that CI systems and package managers frequently can’t do SSH cleanly — but your laptop can.

CI/CD, tool by tool

Bitbucket Pipelines — mostly fine. Pipelines has its own $BITBUCKET_* variables and OIDC. If your pipeline shells out to curl against the Bitbucket API with a hardcoded app password (this is depressingly common for tagging releases or posting PR comments), that’s the bit that breaks. Swap it for a repository access token in a secured variable.

Jenkins — the messiest one, and the Jenkins community threads reflect that. The Bitbucket Branch Source plugin is picky about credential types, and there are open reports of API tokens failing on multibranch pipelines specifically. If you’re on Jenkins, test in a scratch job before the 28th, not on the 28th. A repository or workspace access token stored as a Username/Password credential (with x-bitbucket-api-token-auth as the username for Git operations) is the shape most people have gotten working — but verify against your plugin version rather than trusting me.

GitLab mirroring / importers — pull mirrors from Bitbucket use HTTPS with a stored credential. Update the mirror credential in the repo settings. These fail silently; a mirror that stops syncing doesn’t page anyone, it just quietly goes stale. Check the mirror status page after the cutover.

Tekton Pipelines-as-Code — has an open issue tracking the move to scoped API tokens. Check its status before assuming your current secret survives.

Composer / Packagist — as covered above, the two-username problem makes this genuinely awkward. Private Packagist customers using Bitbucket workspace sync need to re-authorize. Don’t leave this one to the last day.

Terraform / Oracle Resource Manager — anywhere a bitbucket provider or a source control config holds credentials, that’s an app password waiting to expire. Oracle published a specific deprecation notice for Resource Manager’s Bitbucket Cloud connections, which tells you how far the blast radius reaches.

Expiry is now the default, and that’s a new failure mode

App passwords were forever. API tokens are not — they carry expiry, and Bitbucket admins can enforce a maximum lifetime across the workspace.

This is the right security posture and it will absolutely cause an outage in about a year if you don’t plan for it. A credential that never expired required no process. A credential that expires requires someone to notice, on a specific date, that it’s about to.

Two things worth doing while you’re already in here:

Put the tokens in something that manages rotation. If your CI secrets live in a Jenkins credential store and nowhere else, nobody will know a token is expiring until a build dies. A real secrets manager gives you expiry metadata and alerting — I compared Vault, Doppler, and Infisical earlier this year and any of them beats a plugin’s credential list for this.

Set a calendar reminder for two weeks before each token’s expiry. Unglamorous, and it works. The teams that get burned by rotating credentials are the ones who assumed a system would remind them.

Prefer shorter expiry with real automation over a one-year token you’ll forget about. A 90-day token that rotates automatically is safer than a 365-day token nobody owns.

Finding the ones you forgot

The tokens you migrate are the easy half. The dangerous half is the ones nobody remembers.

Search your repos for the patterns app passwords tend to leave:

# app passwords embedded in URLs
git grep -nE 'https://[a-zA-Z0-9._-]+:[a-zA-Z0-9]{20,}@bitbucket\.org' $(git rev-list --all) 2>/dev/null
 
# config files that commonly hold them
git grep -lniE 'bitbucket' -- '*.yml' '*.yaml' '*.json' '*.tf' '*.env*' 'Jenkinsfile*'

If that first command finds anything, the secret is in your Git history permanently, and rotating it was overdue regardless of this deadline. This deadline is just doing you a favor by forcing it.

Beyond the repos, walk the list of places credentials hide: CI credential stores, Kubernetes secrets, .netrc files on build agents, IDE settings synced across machines, that one cron job on the box everyone forgot about. Anything with bitbucket in a config file is a candidate.

The 410s during this week’s brownouts are your friend here. Every system still broken during a brownout window is a system you haven’t migrated. Let the brownouts do the audit for you — that’s what they’re for, and after the 28th they stop being a helpful signal and start being an outage.


If you want a low-effort first step: pull your Bitbucket 4xx logs for the last brownout window and list the distinct user agents. That’s your migration checklist, and it took you no meetings to produce.

Worth sitting with, too — this is the third major credential deprecation in a year, after Azure DevOps’ issuer retirement and the general industry sprint away from long-lived static secrets. The direction of travel is clear enough. Teams that already treat credentials as rotating, inventoried, machine-owned things are doing a config change this month. Everyone else is doing an archaeology project.

Sources: