Skip to content

Bitbucket agents ​

A Bitbucket agent wakes on pull request, comment, and push events from Bitbucket Cloud or Data Center, then writes its results back to the repository. Declare onPullRequest, onPullRequestComment, or onPush on bitbucketChannel() for each event that should wake it.

Connect a repository ​

Name the repository hook secret and list the repositories that may wake the agent.

ts
// bot/channels/bitbucket.ts
import { bitbucketChannel } from "@grok/agent-sdk/channels/bitbucket";

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

Add a repository webhook for https://<your-host>/<slug>/v1/channels/bitbucket with the same secret. On Cursor hosting, a delivery with the wrong secret gets a 401 and never reaches your agent. For Data Center, set the REST base and the API token:

ts
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",
});

For Bitbucket Cloud, omit apiBaseUrl. To set the secrets on a deployment, see Add egress and secrets.

For Bitbucket Cloud on Cursor hosting, you can skip the repository webhook. Set cursorAccount: { repos: ["acme/widgets"] }, and events and ctx.bitbucket calls go through your signed-in Cursor account.

Review a pull request ​

When someone opens a pull request, or pushes new commits to a Data Center pull request, the agent reads the diff and posts its review as a comment on the pull request.

ts
// bot/channels/bitbucket.ts
import {
  bitbucketChannel,
  defaultBitbucketAuth,
} from "@grok/agent-sdk/channels/bitbucket";
import { postPullRequestComment } from "../lib/bitbucket.js";

export default bitbucketChannel({
  repos: ["acme/widgets"],
  webhookSecret: { secretName: "BITBUCKET_WEBHOOK_SECRET" },
  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 helper you write in bot/lib/bitbucket.ts that posts a comment through the Bitbucket API with BITBUCKET_TOKEN. It uses the repository and pull request from the session state, so the model never picks where the review goes.

Each hook returns { auth }, { task }, or null, as handler results describes. All events on the same pull request go to the same session, so the agent sees the whole conversation.

Answer a pull-request comment ​

Skip the agent's own comments, and answer a comment that mentions the agent:

ts
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() as the review handler.

Triage a push to main ​

Mark the new commit as in progress, then start the triage turn:

ts
import {
  bitbucketChannelStateForEvent,
  defaultBitbucketAuth,
} from "@grok/agent-sdk/channels/bitbucket";
import { setPushTriageStatus } from "../lib/bitbucket.js";

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,
          }),
        },
      });
    },
  };
},

When the turn ends, update that same status:

ts
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.

Test with replay ​

Replay a pull request you can read against your local agent, or save the payloads as fixtures. For Data Center, pass the full pull request URL. bitbucket replay lists every flag.

text
agent-sdk dev
agent-sdk bitbucket replay acme/widgets#42 --events '*'
agent-sdk bitbucket replay acme/widgets#42 --events '*' --dry-run --fixtures-dir fixtures/bitbucket