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.
Prerequisites
Section titled “Prerequisites”- A Pro Bridge Town workspace (local execution requires Pro)
- A Bridge Town project with at least one model
- The
btCLI — install withpip install bridgetown-cli - A GitHub repository that holds your project code
Step 1 — Create an API token
Section titled “Step 1 — Create an API token”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:
- Open Settings → Secrets and variables → Actions.
- Click New repository secret.
- Name:
BT_API_TOKEN - Value: the
btk_...token you copied above.
Step 3 — Add the workflow file
Section titled “Step 3 — Add the workflow file”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.
How it works
Section titled “How it works”bt auth token vs bt auth login
Section titled “bt auth token vs bt auth login”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.
bt data pull
Section titled “bt data pull”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.
bt model run --ci
Section titled “bt model run --ci”When the --ci flag is set, the CLI:
- Executes the model in a local subprocess.
- Captures stdout and stderr (each capped at 4 KB before upload).
- Uploads the result to
POST /api/projects/{project}/runson the Bridge Town platform. - 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"}Exit codes
Section titled “Exit codes”bt model run --ci uses standard UNIX exit codes so CI gating works without parsing output:
| Exit code | Meaning |
|---|---|
0 | Model ran and outputs were uploaded successfully |
1 | Model failure — exception raised or subprocess returned non-zero |
2 | CLI / config error — missing credentials, project not found, git error |
3 | Upload 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)”.
bt model diff
Section titled “bt model diff”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.
Gating merges on model correctness
Section titled “Gating merges on model correctness”To require the model-test job to pass before a PR can be merged:
- In your repository, go to Settings → Branches.
- Add a branch protection rule for
main. - 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.
Related
Section titled “Related”- Multi-Model Pipelines — chain models together using
the
PIPELINEconvention;bt model run --ciruns the full pipeline by default - Team Collaboration — project roles and collaborator access
- Branches & Versioning — how Bridge Town branches map to model scenarios