Skip to content

GitHub Actions CI for Bridge Town Models

Bridge Town’s bt model run --ci command runs your model locally, uploads the result to the platform, and exits with a machine-readable status code — making it straightforward to gate pull request merges on model correctness.

This guide walks through the complete setup: creating an API token, wiring it into your repository, and configuring the example workflow.

  • A Pro Bridge Town workspace (local execution requires Pro)
  • A Bridge Town project with at least one model
  • The bt CLI — install with pip install bridgetown-cli
  • A GitHub repository that holds your project code

In Bridge Town, go to Settings → API Tokens and click New token.

Give it a descriptive name (e.g. github-ci) and copy the generated btk_... value. Tokens are shown only once.

Step 2 — Add the token as a repository secret

Section titled “Step 2 — Add the token as a repository secret”

In your GitHub repository:

  1. Open Settings → Secrets and variables → Actions.
  2. Click New repository secret.
  3. Name: BT_API_TOKEN
  4. Value: the btk_... token you copied above.

Create .github/workflows/bridge-town-ci.yml in your repository. A complete example file is included in the Bridge Town repository, or copy the workflow below:

name: Bridge Town model CI
on:
pull_request:
branches: [main]
push:
branches: [main]
env:
BT_PROJECT: revenue-forecast # ← your project name
jobs:
model-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install bt CLI
run: pip install bridgetown-cli
- name: Authenticate
run: bt auth token
env:
BT_API_TOKEN: ${{ secrets.BT_API_TOKEN }}
- name: Pull latest data snapshot
run: bt data pull --project "$BT_PROJECT"
- name: Run model
id: bt_run
run: |
result=$(bt model run --ci --project "$BT_PROJECT")
echo "run_id=$(echo "$result" | jq -r .run_id)" >> "$GITHUB_OUTPUT"
echo "status=$(echo "$result" | jq -r .status)" >> "$GITHUB_OUTPUT"
echo "url=$(echo "$result" | jq -r .platform_url)" >> "$GITHUB_OUTPUT"
- name: Compare outputs to main
if: github.event_name == 'pull_request'
run: |
bt model diff \
--project "$BT_PROJECT" \
--base main \
--scenario "${{ github.head_ref }}"
- name: Post run link to PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const runUrl = '${{ steps.bt_run.outputs.url }}';
const status = '${{ steps.bt_run.outputs.status }}';
const icon = status === 'success' ? '✅' : '❌';
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `${icon} **Bridge Town model run — ${status}**\n\n[View run on Bridge Town](${runUrl})`
});

Replace revenue-forecast with your actual project name.

bt auth login opens a browser window for device-code authentication — this cannot work in a headless CI environment. bt auth token reads BT_API_TOKEN from the environment and writes it to the local credentials store without any browser interaction. Always use bt auth token in CI contexts.

Before running the model, the workflow pulls the data snapshot the model last ran against on the platform. This ensures local execution uses identical inputs to the last platform run, avoiding spurious diffs caused by stale local data.

When the --ci flag is set, the CLI:

  1. Executes the model in a local subprocess.
  2. Captures stdout and stderr (each capped at 4 KB before upload).
  3. Uploads the result to POST /api/projects/{project}/runs on the Bridge Town platform.
  4. Writes a single JSON object to stdout and routes all human-readable progress text to stderr — making it safe to capture the JSON with $(...).

The JSON output looks like this:

{
"run_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "success",
"exit_code": 0,
"duration_seconds": 1.23,
"branch": "scenario/optimistic",
"commit_sha": "abc123def456",
"platform_url": "https://app.bridgetown.builders/models/revenue-forecast/runs/550e8400"
}

bt model run --ci uses standard UNIX exit codes so CI gating works without parsing output:

Exit codeMeaning
0Model ran and outputs were uploaded successfully
1Model failure — exception raised or subprocess returned non-zero
2CLI / config error — missing credentials, project not found, git error
3Upload failure — model ran but POST /api/runs returned non-2xx

Exit code 3 is distinct from 1 so pipelines can tell apart “the model is broken” from “the model worked but result upload failed (transient platform issue)”.

The optional Compare outputs to main step runs bt model diff, which compares the current branch’s model outputs to the main branch. The diff is printed to the Actions log, giving reviewers a compact summary of what changed numerically before merging.

To require the model-test job to pass before a PR can be merged:

  1. In your repository, go to Settings → Branches.
  2. Add a branch protection rule for main.
  3. Under Require status checks to pass before merging, add model-test.

Any PR where the model raises an exception or the outputs fail to upload will now block the merge button.