> ## Documentation Index
> Fetch the complete documentation index at: https://sponson.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Integrations

> Run Sponson from GitLab CI/CD, CircleCI, Bitbucket Pipelines and agent tools.

Copy-paste setups for running Sponson from CI systems other than GitHub Actions, and from agent tools.

| Where | Setup | Destroy on close |
| - | - | - |
| GitHub Actions | [the action](/guides/github-actions), PR comments, production approvals | `pull_request: closed` |
| GitLab CI/CD | [the template](/integrations/gitlab-ci): merge request pipelines | the review environment's `on_stop` job, run by GitLab |
| CircleCI | [the template](/integrations/circleci) | a pipeline started through the API with parameters |
| Bitbucket Pipelines | [the template](/integrations/bitbucket-pipelines) | a custom pipeline you run |
| Claude Code, Cursor, Codex CLI, Windsurf, VS Code | [MCP configurations](/integrations/agent-tools) and where SKILL.md goes | |

Every CI template follows the same life of a preview environment as the GitHub Action: `apply` when the pull request
opens or changes (it stops with status `partial` at the deploy barrier), `apply --wait` once the preview is deploying
(it finishes the lines that needed the preview URL), `apply --destroy` when the pull request closes, and production as
a step a human starts, recorded with `--approved-by`.

What every CI needs, whichever it is:

* **`npx -y sponson@<version>`**, pinned to the version you reviewed, on Node.js 22 or later.
* **The scope flags** `--pr`, `--branch` and `--sha`, filled from the CI's predefined variables. Each template passes
  them explicitly; Sponson 0.5.0 and later also read GitLab's, CircleCI's and Bitbucket's variables themselves
  (as they read GitHub Actions'), see [the run context](/reference/plan-format#context-interpolation-\$ctx-).
* **Provider tokens** as the CI's secret variables, in every job that runs Sponson, destroy included.
* **A token that can push receipts.** The git-branch receipt store pushes orphan branches
  `sponson-receipts/<env>/<scope>` from a clone of its own, so the CI's read-only checkout credentials are not
  enough. Each template sets `SPONSON_RECEIPTS_REMOTE` to an HTTPS URL carrying a write token; its README says which
  token to create. Sponson strips credentials from every git error it prints.

## The pull request comment

The GitHub Action comments on the pull request ([what the comment looks like](/guides/github-actions#the-pr-comment)).
The other templates print to the job log and post nothing, because posting needs a token with more rights than
pushing receipts. To get the same comment on a merge request or pull request, run the command with
`--format markdown` (instead of the default text) and post the file it prints; the exit code is the same, and a
refusal or failure prints a comment too. The first line is the marker `<!-- sponson:<env> -->`: update the note that
starts with it instead of adding one per run.

GitLab, in `sponson:apply`, `sponson:finish` and `sponson:destroy` (a project access token with the `api` scope,
saved as the masked variable `SPONSON_GITLAB_NOTES_TOKEN`; the [merge request notes API](https://docs.gitlab.com/api/notes/#merge-requests)):

```yaml theme={null}
  script:
    - npx -y sponson@0.5.0 apply --format markdown --env preview --pr "$CI_MERGE_REQUEST_IID" --branch "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" --sha "${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA:-$CI_COMMIT_SHA}" > sponson-comment.md || code=$?
    - cat sponson-comment.md
    - |
      node -e '
        const body = require("fs").readFileSync("sponson-comment.md", "utf8"), marker = body.split("\n")[0];
        const api = `${process.env.CI_API_V4_URL}/projects/${process.env.CI_PROJECT_ID}/merge_requests/${process.env.CI_MERGE_REQUEST_IID}/notes`;
        const headers = { "PRIVATE-TOKEN": process.env.SPONSON_GITLAB_NOTES_TOKEN, "content-type": "application/json" };
        (async () => {
          const notes = await (await fetch(`${api}?per_page=100&sort=asc`, { headers })).json();
          const mine = notes.find((n) => n.body.startsWith(marker));
          const r = await fetch(mine ? `${api}/${mine.id}` : api, { method: mine ? "PUT" : "POST", headers, body: JSON.stringify({ body }) });
          if (!r.ok) throw new Error(`posting the note failed: ${r.status}`);
        })();'
    - exit "${code:-0}"
```

Bitbucket Pipelines and CircleCI work the same way: Bitbucket's
[pull request comments API](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-pullrequests/#api-repositories-workspace-repo-slug-pullrequests-pull-request-id-comments-post)
takes the file as `{ "content": { "raw": … } }` (Bitbucket does not render the `<pre>` and `<sub>` HTML the comment
uses for manual steps and its footer; the text stays readable); from CircleCI, post it to the GitHub pull request with
the [issue comments API](https://docs.github.com/rest/issues/comments). A tool that already holds the `--json` result
can render the same markdown with `renderMarkdown(envelope)` from the `sponson` package instead of running Sponson
twice.

## How the templates are checked

**On every `pnpm test`**, `scenarios/docs/integrations.test.ts` parses every template, checks the GitLab template's
structure (stages, jobs, rules, environments and their `on_stop` job) and Bitbucket's (no pipeline starts with a
manual step), and runs every Sponson command line in them through the CLI to the expected scope, with the flags and
through detection of the host's variables alike.

**End to end, in Docker**, `scripts/try-ci-templates.sh [gitlab] [circleci] [bitbucket]` runs each template's whole
preview life against the fake cloud (`@sponson/sim`): a scratch repository whose `main` has a release plan (a Neon
branch, the preview's `DATABASE_URL` from it, a production variable from a secret) and the template, and a pull
request branch that adds a Clerk callback on the preview URL. Per host it runs the pull request pipeline (plan; apply,
which stops at the deploy barrier because the fake deploy takes 20 seconds; finish, which waits for it), checks the
resources exist, runs production with an approver, runs the destroy the host would run when the pull request closes,
checks the preview resources are gone and production kept, and checks the receipts reached the repository. It also
runs the plan command without `--pr/--branch/--sha` in each host's environment and checks the detected scope (pull
request, `main`, and a tag build on CircleCI and Bitbucket). It needs Docker and the network, so it is not part of
`pnpm test`; `KEEP=1` keeps the scratch directory, `SPONSON_TRY_VERSION=0.5.0` runs a release from npm instead of
this checkout.

What is real and what is emulated:

| | Real | Emulated |
| - | - | - |
| Sponson | this checkout, packed with `pnpm pack` and installed by the template's own `npx -y sponson@<version>` line from a local npm registry that serves those tarballs and forwards everything else to npmjs.org (the version in the template is replaced by the packed one, as step 2 of each README asks) | |
| Providers | the adapters' HTTP calls | the fake cloud answers them (`*_API_URL` set in every job) |
| GitLab | [gitlab-ci-local](https://github.com/firecow/gitlab-ci-local) runs `.gitlab-ci.yml` itself in `node:22` containers: GitLab's JSON schema, `workflow:` and job `rules:`, `extends:`, stages, `environment:` names and environment-scoped variables (`review/*`, `production`), manual jobs | the merge request pipeline's predefined variables (`CI_PIPELINE_SOURCE=merge_request_event`, `CI_MERGE_REQUEST_*`, no `CI_COMMIT_BRANCH`) are passed as variables; GitLab itself never runs the `on_stop` job, so `sponson:destroy` is started by name |
| CircleCI | every job's steps, verbatim, in `cimg/node:lts`: each `run` in its own `bash -eo pipefail` with `$BASH_ENV`, the step's `environment:`, pipeline parameters filled in | the checkout (a clone of the scratch repository), `circleci-agent step halt`, the predefined variables, the workflows' order, and CircleCI's API for the approver (a stand-in answering the two documented reads) |
| Bitbucket | every step's `script:`, verbatim and in one shell, in `node:22`, YAML anchors resolved | the clone, the predefined and custom-pipeline variables, deployment variables, the pipelines' order |
| Receipts | pushed by Sponson's own git clone to a bare repository | git rewrites the exact HTTPS URL the template builds (`url.<bare repo>.insteadOf`), so the push only succeeds if the template builds the URL the host's variables give |

Not covered: the hosts' own triggers (when a pipeline starts, GitLab stopping the environment on merge), their
runners' networks and checkout credentials, merged-results pipelines, and real provider APIs. Each README's notes say
which behaviour of the host the template relies on.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.