Skip to content

Keep your project in Git and let a pipeline publish it: every pull request is validated and its tool fixtures run, and every merge to main is evaluated and published. Nobody publishes from a laptop, and the release history matches your commit history.

In CI there is nobody to log in, so the CLI authenticates with an API key instead. See API keys for automation for how keys and scopes work.

  1. Log in as a workspace Owner or Admin, so you can grant agent scopes, and select the workspace you publish to.

  2. Create an API client with the scopes a publish needs, and send its key straight into your repository’s secrets without printing it. With the GitHub CLI:

    Terminal window
    caplo workspace api-client create github-deploy \
    --scope agents:write,agents:publish,tools:write,tools:publish \
    | gh secret set CAPLO_API_KEY

    Add channels:write if an environment deploys channels. Leave out the tools scopes if the project has no tool packages.

  3. Store the workspace ID as CAPLO_WORKSPACE. It is not secret, but keeping it next to the key keeps them in step:

    Terminal window
    caplo workspace get -o json
    gh secret set CAPLO_WORKSPACE --body <WORKSPACE_ID>

With an API key, the workspace must be given by ID, never by name.

Save this as .github/workflows/caplo.yml. It pins the CLI version, so a new CLI release never changes your pipeline until you update the pin.

name: Caplo
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
env:
CAPLO_VERSION: v0.4.0
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- name: Install the Caplo CLI
run: |
curl -fsSL "https://raw.githubusercontent.com/getcaplo/homebrew-tap/$CAPLO_VERSION/install.sh" | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Install tool package dependencies
run: |
for dir in tools/*/; do
if [ -f "$dir/package.json" ]; then npm ci --prefix "$dir"; fi
done
- run: caplo validate
- run: caplo test
publish:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: check
runs-on: ubuntu-latest
concurrency: caplo-publish
env:
CAPLO_API_KEY: ${{ secrets.CAPLO_API_KEY }}
CAPLO_WORKSPACE: ${{ secrets.CAPLO_WORKSPACE }}
steps:
- uses: actions/checkout@v4
- name: Install the Caplo CLI
run: |
curl -fsSL "https://raw.githubusercontent.com/getcaplo/homebrew-tap/$CAPLO_VERSION/install.sh" | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- run: caplo auth status
- run: caplo eval
- run: caplo publish --yes

What each part does:

  • check runs on every pull request and push. caplo validate and caplo test need no key and call no workspace, so they also run for pull requests from forks. npm ci needs each package’s package-lock.json committed.
  • publish runs only on pushes to main. caplo auth status prints the API client and its scopes, which makes a wrong key easy to spot in the log. caplo eval pushes the drafts and runs every agent’s eval cases, so a failing case stops the job before anything is published. caplo publish --yes then publishes bottom up, reusing a passing eval run wherever the draft has not changed since.
  • --yes is required: without a terminal, a publish that would ask for confirmation stops with exit code 2. It also covers the project’s first publish to the workspace.
  • concurrency stops two publishes from running against the same workspace at once.

The install script checks the archive’s SHA-256 checksum before installing. To also gate publishing on a reviewer, put the publish job in a GitHub environment with required reviewers.

To see what a merge would change, add a step to check with a key that has only agents:read and tools:read:

- run: caplo diff
env:
CAPLO_API_KEY: ${{ secrets.CAPLO_READ_KEY }}
CAPLO_WORKSPACE: ${{ secrets.CAPLO_WORKSPACE }}

caplo diff -o json prints the same plan as one JSON document, to post as a comment or check with a policy; see Plans as JSON.

With environments in caplo.yaml, give each its own API client and secret, and select the environment per job:

- run: caplo publish --env staging --yes
env:
CAPLO_STAGING_KEY: ${{ secrets.CAPLO_STAGING_KEY }}

For production, run a separate job, for example on workflow_dispatch or a tag, that sets both keys. With promoteFrom: staging, the CLI reads staging with CAPLO_STAGING_KEY and refuses any agent staging has not released from the same source:

- run: caplo publish --env prod --yes
env:
CAPLO_PROD_KEY: ${{ secrets.CAPLO_PROD_KEY }}
CAPLO_STAGING_KEY: ${{ secrets.CAPLO_STAGING_KEY }}

Do not also set CAPLO_WORKSPACE in these jobs unless it matches the environment’s workspace exactly; a different value stops the command with exit code 2.

AI prompt

Publish my Caplo project from GitHub Actions

Adds a GitHub Actions workflow that validates and tests every change and publishes the Caplo project from main with an API key.

Show prompt
Goal: add a GitHub Actions workflow to this repository that runs `caplo validate` and `caplo test` on every pull request and push, and runs `caplo eval` and `caplo publish --yes` on pushes to `main`, authenticated with a Caplo API key.

Read these pages first:
- https://docs.caplo.si/cli/ci-cd.md
- https://docs.caplo.si/cli/authentication.md
- https://docs.caplo.si/cli/projects.md
- https://docs.caplo.si/cli/scripting.md

Steps:
1. Find the Caplo project: the `caplo.yaml` with `kind: Project`. If it is not at the repository root, run every `caplo` command in the workflow with `-C <PROJECT_DIR>` and adjust the `tools/*/` path. Read its `tools` glob and `environments`, if any.
2. Run `caplo validate` locally and fix any problem it reports before you write the workflow. Do not change agent behavior to make it pass without asking me.
3. Check that every tool package directory has a committed `package-lock.json`. If one is missing, run `npm install` in that package and include the lock file in your change.
4. Create `.github/workflows/caplo.yml` from the GitHub Actions example on the CI/CD page:
   - pin the CLI with `CAPLO_VERSION: <CLI_VERSION>` and install it with the install script from that version's tag;
   - a `check` job with Node.js 24 that installs package dependencies with `npm ci` and runs `caplo validate` and `caplo test`;
   - a `publish` job that runs only on pushes to `main`, needs `check`, uses `concurrency: caplo-publish`, sets `CAPLO_API_KEY` and `CAPLO_WORKSPACE` from repository secrets, and runs `caplo auth status`, `caplo eval` and `caplo publish --yes`.
   If `caplo.yaml` defines environments, use `caplo publish --env <ENVIRONMENT> --yes` and that environment's `apiKeyEnv` secret instead, as the page describes.
5. Do not run `caplo push`, `caplo eval` or `caplo publish` on pull requests.
6. Tell me the commands to create the API client and store the secrets myself, with the scopes the project needs (`agents:write`, `agents:publish`, plus `tools:write` and `tools:publish` if it has tool packages, plus `channels:write` if an environment lists channels):
   `caplo workspace api-client create <CLIENT_NAME> --scope <SCOPES> | gh secret set CAPLO_API_KEY`
   and `gh secret set CAPLO_WORKSPACE --body <WORKSPACE_ID>`.

Verify:
- `caplo validate` and `caplo test` pass locally.
- The workflow file is valid YAML and every `caplo` command and flag in it appears in https://docs.caplo.si/reference/cli/project.md.
- Open a pull request so the `check` job runs, and report its result.

Rules:
- Never create, print, paste or commit an API key, and never put one in the workflow file. Keys go only into GitHub secrets, and I create them.
- Do not run `caplo push`, `caplo eval` or `caplo publish` yourself, and do not merge to `main`, without asking me first: on `main` the workflow publishes to the workspace customers use.
- Do not edit agent files or tool code beyond what validation requires, and ask before any such change.

Add CAPLO_API_KEY and CAPLO_WORKSPACE under Settings, CI/CD, Variables in GitLab, masked and protected so only protected branches can read them. Then in .gitlab-ci.yml:

stages: [check, publish]
variables:
CAPLO_VERSION: v0.4.0
default:
image: node:24
before_script:
- curl -fsSL "https://raw.githubusercontent.com/getcaplo/homebrew-tap/$CAPLO_VERSION/install.sh" | sh
- export PATH="$HOME/.local/bin:$PATH"
check:
stage: check
script:
- for dir in tools/*/; do if [ -f "$dir/package.json" ]; then npm ci --prefix "$dir"; fi; done
- caplo validate
- caplo test
publish:
stage: publish
resource_group: caplo-publish
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
script:
- caplo eval
- caplo publish --yes

Pipelines stop on any non-zero exit code. The code tells you where to look:

Code Meaning in CI Typical fix
0 Success, including nothing to publish
1 An error, such as a refused API key, a missing scope, a network failure or a failed build Read the error line; check scopes with caplo auth status
2 A usage error: a missing --yes, a workspace given by name with an API key, --workspace disagreeing with --env, or channels:write missing for a channel deploy Fix the command or the variables
3 A failed gate: validation, tool fixtures, eval cases, or a promotion with no matching release Fix the files, or publish to the promoteFrom environment first
130, 143 The job was cancelled Rerun it

A publish that fails partway keeps what it already published, such as tool versions or child agent releases. Fix the cause and run it again; unchanged resources are skipped. See Scripting for output formats and environment variables.