v4.1.0
v4.1.0
bb v4.1.0 adds bb doctor for configuration problems and closes most of the remaining gap with gh for pull requests and repositories. It also makes failures say what actually happened: whether a request that never got an answer may have been applied, whether a listing was cut short, and which file or policy refused a command.
It is a minor release with no breaking changes. Most of the behaviour that changes is a fix: v4.0.0's decision records and release notes already specified it, and this release makes the binary keep those decisions everywhere.
Highlights
-
bb doctor(ADR-086, #597): Checks the stored, workspace and system configuration files and reports every problem at once:- schema violations, with their key and line;
- keys a file never reads, such as
require_keyringin a user's own file; - where each effective setting comes from;
- whether a required OS keyring answers.
It needs no Bitbucket host, makes no network call, and exits 1 when there is anything to fix.
-
bb pr view,bb pr editandbb pr closeresolve, andbb pr checksshows a pull request's build statuses.bb pr readymarks a draft ready for review without asking for its version, and--undoturns it back into a draft (#602).bb repo get, also spelledbb repo view, describes a repository and prints its README (#605).
-
An interrupted change is no longer reported as safe to retry (ADR-011, #574):
- The problem. Suppose bb creates a pull request or a comment, or merges one, and the connection drops, times out, or a gateway answers
502before Bitbucket does. Bitbucket may already have applied it. bb used to report that astransient, exit10, which is the code retry wrappers act on, so a retry could create the same pull request or comment twice. - Now. Such a failure is
unknown_outcomeand exits13, outside the retry range, and the message says to check whether the change landed before sending it again. - What still exits
10. A failure before the request left the machine, and a delete or an update whose answer is lost: repeating those cannot create a second anything, so bb retries them itself (ADR-009).
- The problem. Suppose bb creates a pull request or a comment, or merges one, and the connection drops, times out, or a gateway answers
-
Releases you can verify, and a self-update that stays on https unless you allow otherwise. This matters if you check what you install or mirror releases internally; the steps are in Release Verification.
- Per-archive SBOMs (#584). Each release archive now has its own SBOM, published beside it as
<archive name>.spdx.json: the list of every module the binary inside it is built from, which vulnerability scanners and compliance reviews read. The.deband.rpmare covered by the_noupdatearchive's.- It is generated from that binary, because each platform links different modules.
- It is attested against the archive, so
gh attestation verifyproves the SBOM describes that archive. - It replaces the single
sbom.spdx.json, which inventoried the build checkout rather than any binary.
- https for update mirrors (ADR-059).
bb updatefetches release mirrors overhttps, because over plain HTTP anyone on the network path can read or hold back what the mirror serves.- A mirror without TLS needs
bb update --allow-httporBB_ALLOW_HTTP_UPDATE=1; without one of them the update is refused (exit2). - In system policy,
allow_http_update: truepermits it for every user without the flag, andallow_http_update: falserefuses it for every user (exit3).
- A mirror without TLS needs
- Pinned actions. The workflow that builds and signs releases runs GitHub Actions pinned to exact commits, so a moved tag cannot change what builds the binary.
- Windows and macOS CI. The unit suite runs natively on Windows and macOS as well as Linux.
- Per-archive SBOMs (#584). Each release archive now has its own SBOM, published beside it as
Fixes: v4.0.0 decisions the binary now keeps everywhere
- Every destructive delete confirms (ADR-073).
- The rule: destructive commands confirm when a person is present and require
--yeswhen not, and--yesis inert on a repository inferred from the git remote. - v4.0.0 enforced it for
bb repo delete,bb repo admin deleteandbb auth gpg-key clear. Every command whose last word is delete, remove, revoke or clear now follows it: 37 of them, includingbb branch delete,bb tag delete,bb webhook delete,bb build delete,bb insights report delete,bb pr review reviewer remove,bb repo permissions revokeandbb auth token revoke. - In a pipeline, pass
--yesand name the target explicitly:--repo PROJECT/slug, orBITBUCKET_PROJECT_KEYandBITBUCKET_REPO_SLUG. A repository bb took from the git remote does not count, and--jsonneeds--yeseven at a terminal.
- The rule: destructive commands confirm when a person is present and require
- Invalid arguments exit
2, as the v4.0.0 notes promised. A subcommand that a command group does not know printed the group's help and exited0, which put prose on stdout under--json. It now exits2(validation), and the help goes to stderr. - Error kinds match the taxonomy (ADR-011, #574).
- A rejected TLS certificate or a DNS failure is
permanent(exit1). It is nottransient(exit10), which invites a retry that cannot succeed. - A
401that refuses a known user isauthorization, notauthentication.
- A rejected TLS certificate or a DNS failure is
- A capped listing says it was capped (ADR-074, #573).
--limithas capped the total since v4.0.0. Every listing that takes it now also reportsmeta.limitReached, and MCP results (limit_reached) and text output say so too.bb insights annotation listalso applies the default limit of 25 it had been ignoring; pass--allfor every annotation.
Other fixes worth knowing
- Error messages no longer carry raw Bitbucket responses.
- An upstream response body is summarised, and credentials in it are redacted, before it reaches a message. Some bodies ran to 18 KB.
error.detailscarriesupstreamStatusand, when Bitbucket names one,upstreamException.--full-error-bodyprints the whole body when you need it.
--dry-runchanges nothing on the local machine either (#571). Twelve commands used to apply their change under--dry-runand now preview it:bb auth login,logout,alias add,alias discover,alias remove,server use,setup-git,bb ai skill installandskill remove,bb clone,bb repo cloneandbb pr checkout.bb ai mcp serve --dry-runis refused rather than started, because a running server cannot be previewed (#568).- A configuration file bb cannot read is an error, not an empty file (#567).
- Commands name the file, point at
bb doctor, and stop withpermanent(exit1), instead of reporting that you are not logged in withvalidation(exit2). bb auth loginandbb auth logoutno longer rewrite a file they could not read, which removed every other host.BB_DISABLE_STORED_CONFIG=1no longer reads the stored file at all.
- Commands name the file, point at
Deprecation
bb bulkis deprecated and is removed in v5.0.0 (ADR-084).- It warns once per invocation on stderr, so
--jsonoutput stays clean. - Permissions, webhooks and default tasks set at project level already apply to every repository in the project. For anything else, loop over
bb repo list.
- It warns once per invocation on stderr, so
The full change ledger follows.
Changes since v4.0.0.
Compare: v4.0.0...v4.1.0
All 196 changes
Features
- update: update URLs are https unless plain HTTP is explicitly allowed (7c3c01e)
- bb doctor checks the configuration bb would load (fe6962d)
- repo: bb repo get describes a repository and prints its README, with gh's view as an alias (3152a9a)
- pr: bb pr ready marks a draft ready for review without asking for its version (dc27824)
- errors: unknown_outcome, for a request whose result never came back (7dfbf6b)
- safety: install the destructive confirmation from one walk (a3621a2)
- safety: confirm before deleting a branch, a tag or a webhook (56ed0a4)
- bulk: deprecate bb bulk, and add the machinery that remembers (7d6332d)
- pr: register the gh spellings so muscle memory resolves (bbd53fa)
Fixes
- release: ask jq for the one conclusion instead of piping into head (72b82fa)
- release: keep a prerelease tag from deciding the next release (b6de809)
- pr: let each half of an alias name the other, and prove doctor asks nothing (812a9b9)
- errors: ignore a status that is not one, and tidy what the refactors left (1f726b5)
- quality: count the endpoints bb calls from outside the services (09d6385)
- errors: a 2xx is not proof that a mutation landed (0518320)
- update: read the mirror as policy, and warn on the plain HTTP it reaches (ed04460)
- pr: read the version bb pr update needs instead of demanding it (e0d699b)
- cli: answer a request for help instead of refusing it (44a66a4)
- cli: make the destructive confirmation say what it means (7c0b200)
- errors: keep the transport's classification through three wrappers (3b1f04f)
- auth: give bb auth status its TLS remedy back (d15f3a0)
- errors: a status that arrived is the outcome (6d4346c)
- errors: a server that refuses the handshake is permanent (54f3790)
- doctor: report the refusals bb update would meet (51bad61)
- config: do not let an unreadable registry policy switch a control off (7fe30c9)
- errors: close the redaction gaps an upstream body can use (1eaf15a)
- git: keep a clone credential out of git's command line (4243b75)
- config: bind a stored credential to the host it was stored for (ba23a13)
- auth: keep the git remote out of a token's scope (d3f093c)
- cli: answer an interrupt that finds bb blocked on a read (8757850)
- errors: show a JSON envelope under --full-error-body too (edcf1ea)
- test: wait for Bitbucket to rescope the pull request before rebasing it (56ffc9f)
- update: say which address was refused, and which policy refused it (a6c3502)
- config: find a path part's name on disk in one pass that Linux runs in full (7963ded)
- config: one keyring key per config file where the file system ignores case (e439e43)
- errors: a 401 that refuses a known caller is authorization (b1060e6)
- doctor: bb doctor exits 1 for any issue, with every issue in error.details (ac68f3b)
- pr: review complete without a draft review names the command that sets the status (af1d650)
- mcp: list_pull_requests answers with your own pull requests, and the tool listing says which tools write (0d8778c)
- webhook: a create bb could not hear the answer to looks before reporting (f7d8427)
- describe: an echoed flag publishes the flag's own values, and bb bulk describes its full artifacts (4784c1b)
- repo compare --diff shows what the branch adds, and an old keyring token no longer overrides a new login (7dc2323)
- config: logout, bb update and the CI variable no longer run past a damaged config (13a3d80)
- errors: every command reports a rejected certificate as permanent and a lost mutation as exit 13 (a29b051)
- output: insights annotation list honours --limit, and three listings report reaching it correctly (b8fd5a8)
- output: a capped result says so in MCP results and text output too (209dca0)
- output: meta is published open to new fields, and pr comment list reports meta.limitReached (7be0759)
- output: every capped listing says so, and the envelope has a published contract (34928ea)
- errors: redact an upstream body before it becomes a message (dd583fc)
- errors: summarise an upstream body, and stop calling a rejected certificate transient (b693064)
- describe: publish the vocabulary the flag accepts, and the shape bulk emits (11bfe39)
- config: a hand-edited profile whose URL and map key differ keeps its stored credential (674bafb)
- cli: say what a command wanted, and give compare the diff it promises (096fbe3)
- pr,config: name the pull request that was approved, and scope the keyring (3c63562)
- config: a file bb cannot read is not a file that is not there (0a400f2)
- test: give each test server its own transport, not the shared one (9a70af5)
- test: bump the version before the target moves, not after (b00e853)
- quality: find a generated call by its method, not by its receiver's name (cfd0984)
- test: derive the project name, so one aborted run does not block the rest (1ffc7a7)
- dry-run: stop the local registry meaning "run it anyway" (6d2a048)
- test: a live test that cannot run its case now fails instead of skipping (e2bb3b5)
- test: make the webhook delivery tests actually test delivery (75867f7)
- pr: keep the rebase call visible to the spec-coverage detector (8320d4f)
- pr: recover from a stale version bb read itself when rebasing (a7450cd)
- test: clone into a temp directory, not into the checkout (c544838)
- test: seal the packages that were reading the developer's own config (e825cef)
- cli: fail when a command group is handed an unknown subcommand (cb37d0e)
Performance
- paging: stop walking a filtered listing one row at a time (92d87ed)
Refactors
- cli: make the interrupt answer testable (06cea58)
- errors: keep --full-error-body in an atomic, not a global the root command races on (c18f675)
- deprecation: ask the registry question of a list, not a global (c713845)
Docs
- say which registry spellings policy accepts, and what an unreadable one does (383b064)
- adr: say what the release process and the deprecation window actually do (77ea917)
- name the Windows registry values policy is read from (eb217cd)
- stop teaching the deprecated command, and write verification once (d0662ae)
- fix the small things that were wrong (acb8471)
- state what is true rather than what changed (0cb75a0)
- document the .env files bb reads (72b2c2a)
- close the gaps a gh user falls into (c87999b)
- describe the llms.txt that exists, and document bb ai skill install (72d1429)
- add the three failures people hit most, and fix the keyring remedy (9ed3cec)
- link troubleshooting and gh parity from where a reader is stuck (b46adcd)
- correct the bb auth status examples against the binary (e872fb0)
- say bb bulk is deprecated where agents are sent to it (d3f82f2)
- record that a credential is bound to its host (a0e9af7)
- rebase onto next, which is where every change goes (ac80155)
- fix claims that do not match the binary or the release (85e8018)
- correct the v4.1.0 release notes (34ef3e4)
- name go.mod as the Go version contributors need (fcb3e1b)
- explain exit code 13 and the release integrity work in the v4.1.0 notes (56db8d4)
- frame the v4.1.0 behaviour changes as the fixes they are (bd2ad90)
- introduce the v4.1.0 release (0c52f52)
- render the cheatsheet's shell completion tabs (bbef023)
- put the plain-HTTP refusal where users look, and release verification out of the quickstart (c05691c)
- name the SBOM the .deb and .rpm are attested with, and where nfpm runs (652ae31)
- pr: say what bb pr ready does, not the chore it replaces (78175ad)
- adr: ADR-062 says why list_pull_requests alone takes an optional repository (1adb06e)
- adr: the MCP gating rule and the project-scoped dashboard, as the code has them (9f7b8c2)
- say what bb does, not what changed (5b32900)
- records and guides describe the error, config, keyring and truncation changes (bacc45e)
- tell the human audience that Cloud is unsupported (3a3dcd8)
- keep the deprecation out of the record and out of the ranking (34149a1)
- show what bulk and dry-run actually print (7ef40b1)
- grow the bulk and dry-run pages into guides (88ea4d7)
- bb api is the same thing as gh api, so stop saying it is not (ccb7c92)
- correct what stands in for gh repo view (78a51d8)
- collapse the global flags, and print one open copy at the top (ccc030d)
- say what changes when you arrive from gh (e10f682)
- stop putting counts and reassurance in a hand-written page (4d19a79)
- generate the MCP tool reference from the registry that serves it (85e9dcd)
- regenerate the ADR page the merge order left behind (456d039)
- boost the troubleshooting page in search (4d5ee12)
- weight search so task pages outrank decision records (7c447aa)
Tests
- releasetags: guard the repository the tag test builds (58bf53e)
- config: assert on both platforms instead of skipping on one (df5dd91)
- quality: gate the mocked-server inventory and let it see every server (9f45b73)
- live: fail where the suite used to skip, and find what that hid (d0aa54b)
- bulk: run the deprecation warning instead of trusting it (589a651)
- testsupport: catch the names a clock reaches through assembly (2eeac73)
- pr: prove the stale-version retry reads the version again (483c429)
- cli: run the delete confirmation rather than check its flag (1b343e4)
- ai: expect the project skill path under the working directory the OS reports (7dc16fc)
- update: remove the WSL-only Windows swap smoke test (1b3bb42)
- live: the label and watch lifecycle runs alone (7f7121a)
- openapi: the retry test sends through its server's own transport (0aec4a9)
- point every transport fault at a port no listener is handed (6fd023a)
- cover the failure paths of bb pr ready and bb repo get (4f41e8f)
- docs: check quoted error messages against the source that prints them (962f6c4)
- webhook: name the create's command words where command-reach reads them (f3f0138)
- cover the failure paths the review fixes added (8cb9f43)
- webhook: a create response lost to Bitbucket's serialisation race is not a failed create (74d1635)
- transport: close the responses the outcome tests discard, and justify the doer's G704 (22de971)
- the clock-name guard reads the code, and ADR-085 says what it enforces (6356f4e)
- output: check every JSON write under --limit, not whether the command reports once (685edc2)
- a test fixture is named at random rather than from the clock (ADR-085) (5f1cce9)
- pr: make the rebase transport-fault test reach the rebase call (de90514)
- pr: cover the rebase retry the live suite cannot reach (2f909c0)
- deprecation: cover what a malformed version does (2005d0f)
- live: read bulk output from stdout alone (8326664)
- cli: cover the cases UnknownSubcommandError must stay quiet for (8262b4d)
Build
- docs: silence Material's MkDocs 2.0 advisory in every docs task (d1a761f)
- stack: stop at 2h58m, and restart an instance 2h40m old before a live run (1a8e9fa)
- stack: quieter pruning, and no empty row in the instance cap message (59d0e7c)
- stack: one local Bitbucket instance per checkout (35698fb)
- stack: the local Bitbucket stops itself before its licence runs out, and test:live starts it (25c080a)
- error-registry: each row's kind comes from bb's own mapping (83217b6)
- record the readme operation bb repo get calls (e1135d9)
- publish the docs under each release's major version (77b5b06)
- docs:verify-generated reports stale docs instead of hanging (c866d51)
CI
- measure a patch against the branch it targets (1190aa5)
- release: let a prerelease be a prerelease (593e9f0)
- release: keep the docs toolchain out of the job that can sign a release (dbb15b9)
- ask for the check the promotion push skips, and stop expanding a branch name (b9153d4)
- make the Dependabot base hold fail when it stops holding (5825e5b)
- propose Dependabot updates against next (45c68d9)
- release: generate each archive's SBOM from the binary inside it (1a7314a)
- pin every GitHub Action to the commit of its latest release (518899a)
- run the unit suite natively on Windows and macOS (ced6604)
- a restarted local stack refused every live test and said nothing useful (a44d9d3)
Chores
- run govulncheck v1.8.0, which can analyse go1.27 code (698f427)
- run the gofmt and golangci-lint the pinned toolchain needs (e0515be)
- deps: build with go1.27.1 and format for its gofmt (a2fd62f)
- deps: bump the gomod-minor-patch group across 1 directory with 12 updates (4bcfa06)
- deps: bump atlassian/bitbucket in /docker/harness (0bb98c8)
- deps: bump astral-sh/setup-uv from 10.0.1 to 10.1.0 (49098a4)
- deps: bump the gomod-minor-patch group with 13 updates (ac5cc7e)
Other
- State that the schema exists, and stop prescribing how to run it (b7f9859)
- Validate config with what the reader already has (1a8e230)
- Name what a trace keeps, and validate config instead of reading it (019271c)
- Say what a diagnostic file contains before asking anyone to send it (4038c52)
- Propose a deprecation window, for a decision rather than as one (d07fc61)
- Put troubleshooting where a stuck person looks for it (fe107ab)
- Move the self-update builds to the guide that deploys them (23d15e0)
- Stop certifying a command the shell will not run (c566d40)
- Explain the build variant most installs actually deliver (432ef74)
- Give the system policy keys a reference page (ecefffd)
- Put back an example I moved under a claim it contradicts (7bfdff8)
- Correct the two comments that said llms.txt could not be macroed (b192b87)
- Render macros into the files mkdocs copies without rendering (5845d92)
- Macro the other version the docs had typed out by hand (a6f09fe)
- Read the tested Bitbucket version from the stack that is tested (1827b62)
- Separate the two compatibility questions, and keep next alive between bundles (84201c7)
- Say which Bitbucket versions bb is tested against (de0d457)
- Point at the contributor guide, and stop naming tests that were deleted (0f813c2)
- Print the live coverage number instead of computing and withholding it (340c6c7)
- Ship the Apache licence rather than a paraphrase of it (8491938)
- Stop directing Arch users at a package nobody has registered (dda59ac)
- An ADR may not send work to a branch the gate refuses (40893db)
- Teach docs-lint that a version can hide in a filename (b3299c4)
- Document the fleet controls an administrator cannot currently find (836f5e1)
- Say what the threat model can actually claim (b90448a)
- An accepted ADR may not name a flag bb does not have (36bfaa0)
- Record the v4 credential decision instead of editing the old one (f6420e5)
- Teach docs-lint the two things it could not see (8256737)
- Correct documented outputs that no longer match what the commands emit (d547520)
- Correct v4 docs that still describe removed flags and fields (50097d8)