This file provides guidance to Claude Code when working with this repository.
The PR review workflow enforces two rules the automated reviewer applies on every pull request:
- Tests ship with code. If a PR changes application or library source code in a way that warrants tests (new or changed behavior, bug fixes, new branches or edge cases) and does not add or update corresponding tests, the reviewer flags it as a HIGH-severity finding. Docs-only, README, comments, formatting, and pure-configuration changes (CI YAML, lockfile bumps, asset-only, version bumps) are exempt.
- Semgrep findings inform the review. A free Semgrep (OSS) scan runs before the Claude review and posts its findings as a PR comment, which the reviewer folds into its analysis. It scans with the
p/python,p/bash,p/secrets, andp/cirule packs. This replaces the metered Claude security-review job.
bb is a Bitbucket Cloud client with two parallel implementations of the same REST contract:
- Bash CLI (
bb) — human-facing, depends only oncurl+jq. - Python MCP server (
mcp_server.py+bb_api.py+bb_ops.py+git_ops.py) — exposes the same Bitbucket surface plus git-context helpers as MCP tools that Claude Code (or any MCP-aware client) can call directly.
Both implementations target Bitbucket Cloud REST API v2.0. Neither wraps the other — they speak HTTP directly.
bitbucket-cli/
├── bb # Bash CLI (cmd_* functions, ~1.4k lines)
├── bb_api.py # urllib-based HTTP client, pagination, redacting
├── bb_ops.py # Bitbucket operations: pipelines / PRs / repos / branches / vars / commits
├── git_ops.py # subprocess wrappers: branch / status / remote / commits / diffs
├── mcp_server.py # FastMCP tool registry + self-bootstrap venv
├── agents/bitbucket.md # Generic agent definition for the MCP server
├── tests/ # pytest suite (~360 tests)
├── pyproject.toml # Python packaging + pytest config
├── docs/img/ # social-preview.png and other assets
├── README.md # User-facing docs (install, usage, MCP setup)
├── CONTRIBUTING.md # Parity rule, GitFlow, branch protection
├── SECURITY.md # Disclosure policy + maintainer checklist
└── .github/ # CI, Claude code-review / security-review workflows, CODEOWNERS
- Languages: bash (3.2+ — macOS system bash is supported) and Python (3.10+)
- API: Bitbucket Cloud REST API v2.0 (
https://api.bitbucket.org/2.0) - Auth: HTTP Basic using Atlassian API tokens
BB_USER: Bitbucket account emailBB_TOKEN: API token from id.atlassian.com (never echoed)BB_WORKSPACE: workspace slug
- MCP runtime: stdlib-only at runtime; the
mcppackage is the only third-party dep, installed into a self-bootstrapped venv at$XDG_DATA_HOME/bitbucket-cli/venv(default~/.local/share/bitbucket-cli/venv) on first invocation.
Loaded in this order (later overrides earlier):
~/.config/bb/config— user config.envin script directory — local override (gitignored)- Environment variables (highest priority)
- User-facing commands:
cmd_<name>functions. - HTTP helpers:
bb_get/bb_post/bb_put/bb_delete. resolve_reposetsrepo+BB_WORKSPACEin the CALLER's scope (called asresolve_repo "$1", NOTrepo=$(...), so it escapes the subshell and can set the workspace). Precedence:-wflag >workspace/slugarg > git origin auto-detect >BB_WORKSPACEdefault > error.resolve_workspaceis the repo-less companion for workspace-level commands. Both mirror the Python_resolve_repocontract.BB_WORKSPACEis OPTIONAL — onlyBB_USER+BB_TOKENare required at load; a missing workspace fails at the point of use, not at startup.- Boundary validation via
_require_*helpers, one per user-supplied value that reaches a URL or a jq program:_require_build_number/_require_pr_id/_require_step_index(numeric guards — the last two also close jq/URL injection surfaces,_require_step_indexspecifically prevents$ENV.BB_TOKENexfiltration through the logs jq program),_require_countand_require_pr_state(query-param injection). Every value Python validates (_is_positive_int/_validate_pr_id) MUST have a bash_require_*sibling — parity gaps here are security bugs, not cosmetics. - User-supplied values ride into JSON via
jq(--arg/--args), never string interpolation, to prevent injection. Delimited streams are off-limits for value lists: bash arguments are C strings, so a NUL delimiter embedded in a jq program is silently dropped by the shell (jq seessplit("")and shreds the stream per character), and any printable delimiter can collide with a value.bb triggerpasses eachKEY=VALUEpair as its own jq argument via$ARGS.positional(jq 1.6+). - Error rc capture:
local rc=0; out=$(cmd) || rc=$?— NOTif ! cmd; then rc=$?(the!negation makes$?always 0; verified on bash 3.2 + 5.x). - bash 3.2+ floor (macOS system bash supported) — no
${var,,}/${var^^}/mapfile/declare -A; abash:3.2CI job (bash32-floor) parsesbbto catch regressions.
BBClientinjected as first arg into everybb_opsfunction.bb_ops.<verb>_<noun>naming (e.g.pipeline_trigger,pr_create)._is_positive_intguard rejects bool (the bool-is-int trap).- MCP tools resolve repo via
_resolve_repo(rejects malformed/./../whitespace BEFORE any network call). - Error envelopes route ALL string fields (
message,body,stderr,url) through_safe_text/_redact_url— single chokepoint, not per-field. _error_dict_with(e, ...)threads request identifiers (pr_id, number, step_index) into the error dict for correlation.
The bash and Python sides implement the same Bitbucket REST contract independently. When a defect surfaces in either side (URL construction, body shape, parameter naming), the fix lands in BOTH paths. Tests verify the correct contract — never pin existing buggy behavior. See CONTRIBUTING.md.
Never default a destructive or irreversible action to on. A flag or field that deletes, closes, or overwrites something (close_source_branch, force-flags, --yes-style skips) defaults to the safe value; the caller opts in explicitly. This matches gh (gh pr merge keeps the branch unless you pass --delete-branch). History that motivated the rule: close_source_branch was hardcoded true, which deleted a repo's develop branch on a promote-PR merge (the 2026-07 ryan-os incident). A name-pattern "guard" that guesses whether to delete (v1.6.0's long-lived-branch heuristic) is still a destructive default and was removed — the fix is to not delete by default at all. Do NOT reintroduce delete-by-default or an auto-guess guard.
For end-user CLI features:
- Add
cmd_yourcommand()inbb. - Wire into the case statement at the bottom of the script.
- Add help text in
cmd_help(). - Update README.md usage examples.
For MCP-tool features:
- Add
bb_ops.<verb>_<noun>()Python function. - Add pytest coverage in
tests/(assert URL + method + body shape — not just status). - Add a thin
@mcp.tool()wrapper inmcp_server.pythat calls_resolve_repo, invokes the ops function, returns{"ok": True, ...}on success and_error_dict_with(e, ...)on failure. - Update the tool-surface table in
agents/bitbucket.md.
If the new feature applies to both surfaces (the dominant case), do both. Parity is the default.
# Python tests (run from repo root)
pytest
# Specific test file
pytest tests/test_mcp_server.py -v
# Bash smoke tests (manual, against your workspace)
bb whoami
bb repos
bb pipelines your-repoCI runs pytest on every PR. Bash side is smoke-tested manually before release.
- BB_TOKEN never echoed (whoami, error dicts, log lines).
- URL credential leaks (
https://user:token@host) stripped in all redactors. - Signed-URL query parameters (AWS X-Amz-Signature, Azure SAS, GCP signing, bearer / access_token / api_key) stripped from error URLs.
- Cross-host Authorization stripping on redirect (so the Bitbucket Basic header never reaches S3).
- Pipeline variable values masked as
KEY=***when echoed back to the user. - subprocess calls use
GIT_TERMINAL_PROMPT=0,GIT_ASKPASS="",stdin=DEVNULL, timeout — no interactive prompts can hang the MCP server. - See
SECURITY.mdfor the full posture and disclosure policy.
- Manual pipeline step approval requires the Bitbucket UI (REST API doesn't support it).
- Diffs from
git_uncommitted_changesare capped at 1 MiB (with truncation marker); untracked file list capped at 10 000 entries. - Rate limiting is not handled explicitly (Bitbucket Cloud has generous limits; if you hit one, the MCP error envelope surfaces the 429 cleanly).