Skip to main content

Command Palette

Search for a command to run...

BDK

Bitbucket agents

A Bitbucket agent wakes from a verified repository event, reads the current pull-request or commit state, and writes the result back to Bitbucket. bitbucketChannel() gives the same hooks and normalized payloads on Bitbucket Cloud and Data Center.

Review a pull request

Creating a pull request, or pushing new commits to a Data Center pull request, starts a review. The hook reads the current diff through the repository-bound API, the model returns a verdict, and the completed answer appears as a comment on the pull request.

// bot/channels/bitbucket.tsimport {  bitbucketChannel,  bitbucketChannelStateForEvent,  defaultBitbucketAuth,} from "@cursor/bdk/channels/bitbucket";import {  postPullRequestComment,  setPushTriageStatus,} from "../lib/bitbucket.js";export default bitbucketChannel({  repos: ["acme/widgets"],  onPullRequest: async (ctx, pullRequest) => {    if (pullRequest.draft) {      return null;    }    const hasNewCommits =      pullRequest.action === "updated" &&      pullRequest.previousHeadSha !== undefined &&      pullRequest.previousHeadSha !== pullRequest.headSha;    if (pullRequest.action !== "created" && !hasNewCommits) {      return null;    }    const diff = await ctx.bitbucket.api.getPullRequestDiff(pullRequest.id);    return {      // `auth` starts a model turn running as the event sender.      auth: defaultBitbucketAuth(ctx),      message: `Review ${ctx.repository.fullName}#${pullRequest.id}.`,      context: [`<bitbucket_diff>${diff}</bitbucket_diff>`],    };  },  events: {    async "message.completed"(event, channel, ctx) {      if (ctx.session.purpose === "eval") {        return;      }      if (event.data.finishReason === "tool_call" || event.data.text === "") {        return;      }      const pullRequestId = channel.state.pullRequestId;      if (        !channel.state.eventType?.startsWith("pullrequest:") ||        pullRequestId === null      ) {        return;      }      await postPullRequestComment(        channel.state,        pullRequestId,        event.data.text      );    },  },});

postPullRequestComment is a small wrapper around the normalized Bitbucket API. Configure it with the same token and API base described under Connect. It takes the repository and PR ID from the verified event state, so the model never chooses where its review is posted.

Answer a pull-request comment

Mentioning @cursor-reviewer in a PR comment resumes that pull request's conversation, and the first example's reply sink posts the answer back to the same PR. The author guard ignores comments written by the agent, preventing each reply from starting another turn.

const AGENT_USERNAME = "cursor-reviewer";onPullRequestComment: (ctx, comment) => {  if (comment.author?.username === AGENT_USERNAME) {    return null;  }  if (!comment.body.includes(`@${AGENT_USERNAME}`)) {    return null;  }  return {    auth: defaultBitbucketAuth(ctx),    message: comment.body,  };},

Add this property to the same bitbucketChannel() declaration as the review handler.

Triage a push to main

A push to main immediately adds a running cursor/push-triage build status to the new commit. The webhook is acknowledged while the model investigates, then the status reports whether that triage turn completed.

onPush: (ctx, push) => {  const change = push.changes.find(    candidate =>      candidate.name === "main" && candidate.after !== undefined  );  if (change?.after === undefined) {    return null;  }  const sha = change.after;  return {    // `task` runs this host sequence after the webhook is acknowledged.    task: async ({ send }) => {      await ctx.bitbucket.setBuildStatus({        sha,        key: "cursor/push-triage",        state: "INPROGRESS",        name: "Cursor push triage",        description: "Investigating push to main",      });      await send(`Triage push ${sha} on ${ctx.repository.fullName}.`, {        auth: defaultBitbucketAuth(ctx),        title: `Bitbucket push ${sha}`,        state: {          ...bitbucketChannelStateForEvent({            repository: ctx.repository,            headSha: sha,            eventType: ctx.eventType,          }),        },      });    },  };},

Extend the events map so the turn's terminal event concludes the status through the same provider wrapper:

async "message.completed"(event, channel, ctx) {  if (    ctx.session.purpose !== "eval" &&    channel.state.eventType === "repo:push" &&    channel.state.headSha !== null &&    event.data.finishReason !== "tool_call"  ) {    await setPushTriageStatus(      channel.state,      "SUCCESSFUL",      "Push triage complete"    );    return;  }  // Continue with the pull-request reply branch from the first example.},async "turn.failed"(_event, channel, ctx) {  if (    ctx.session.purpose === "eval" ||    channel.state.eventType !== "repo:push" ||    channel.state.headSha === null  ) {    return;  }  await setPushTriageStatus(    channel.state,    "FAILED",    "Push triage failed"  );},

This status reports the triage run, not the health of the pushed commit itself.

Events

Use onPullRequest, onPullRequestComment, or onPush for the events shown above. Each hook returns one of three decisions:

ReturnWhat happens
{ auth }Start a model turn as the Bitbucket event sender
{ task }Acknowledge the webhook, then run host code that may also call send
nullSkip the event

The Channels reference covers the remaining hook and session behavior.

Pull-request affinity

Every pull-request event and comment for the same repository and PR ID resumes one agent session. A new PR, later commits, and a mention in the discussion therefore share the same conversation across both Bitbucket dialects.

Call Bitbucket through ctx.bitbucket

Each hook receives an API client already bound to the repository and dialect from the verified delivery. Use ctx.bitbucket.api for pull-request reads and comments, setBuildStatus() for one status update, or createCheck() when a status changes throughout longer work.

The repository does not come from model output. Keep API calls on this bound client so the event decides where reads and writes go.

Connect

On Cursor-managed hosting, use the signed-in Cursor account and list the repositories that may wake the agent:

export default bitbucketChannel({  cursorAccount: {    repos: ["acme/widgets"],  },});

cursorAccount covers event delivery and API calls made through ctx.bitbucket inside hooks. The postPullRequestComment and setPushTriageStatus wrappers are separate REST clients, so configure their Bitbucket API credential too.

For direct webhooks, configure the repository hook secret, API token, and allowed repositories. Data Center also needs its REST API base:

export default bitbucketChannel({  repos: ["PLATFORM/api"],  webhookSecret: { secretName: "BITBUCKET_WEBHOOK_SECRET" },  token: () => process.env.BITBUCKET_TOKEN ?? "",  apiBaseUrl: "https://bitbucket.example.com/rest/api/1.0",});

Without webhookSecret, the direct production route stays loopback-only. Setting it admits deliveries signed with the matching repository-hook secret; token authorizes the API reads and writes in the examples.

Name the secret rather than reading it. Locally the channel reads that environment variable, and on Cursor hosting the platform verifies X-Hub-Signature with the deployment secret of that name at ingestion, so an unsigned or mis-signed delivery gets a 401 before any compute starts. architecture: "v2" requires the named form and bdk validate rejects a literal or () => process.env.X getter there; set the value with bdk secrets set <slug> BITBUCKET_WEBHOOK_SECRET.

Register the repository webhook at https://<your-host>/<slug>/v1/channels/bitbucket with the same secret. For Bitbucket Cloud, omit apiBaseUrl.

Test locally

Replay lets you exercise the same hooks with a pull request you can read before registering a live repository webhook.

bdk devbdk bitbucket replay \  https://bitbucket.example.com/projects/PLATFORM/repos/api/pull-requests/42 \  --events '*'

Use --dry-run --out fixtures/bitbucket to save the payloads instead of posting them. BITBUCKET_TOKEN needs read access to the pull request.