Skip to content

GitHub Actions Integration

Running canopy ci in GitHub Actions surfaces broken imports, dead exports, circular dependencies, and custom plugin violations as inline PR annotations. Reviewers see findings directly on the diff — no external dashboards required.

  • Canopy license key (Solo or Pro — canopy ci is not available in Community tier)
  • License key stored as a GitHub Actions secret named CANOPY_LICENSE_KEY
  • A repo with a Canopy index committed or built in CI (see CI Cached Indexes for caching)

In your GitHub repo: Settings → Secrets and variables → Actions → New repository secret.

Name: CANOPY_LICENSE_KEY
Value: your Canopy license key

Create .github/workflows/forge.yml:

name: Canopy Health Check
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
forge-health:
runs-on: ubuntu-latest
permissions:
pull-requests: write
checks: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Canopy
run: |
curl -fsSL https://downloads.canopy.ironpinelabs.com/releases/latest/forge-linux-x86_64 \
-o canopy
chmod +x canopy
sudo mv canopy /usr/local/bin/canopy
canopy --version
- name: Activate license
run: canopy activate ${{ secrets.CANOPY_LICENSE_KEY }}
env:
CI: true
- name: Index repo
run: canopy index . --with-search --with-git
- name: Run health check
run: canopy ci --repo . --format github --fail-on-p0

With --format github, Canopy writes GitHub Actions workflow commands to stdout. These render as inline annotations on the PR:

::error file=src/payments/checkout.ts,line=45,col=1::broken_import: cannot resolve '../lib/stripe-client'
::warning file=src/utils/format.ts,line=12,col=1::dead_export: 'formatCurrency' has 0 consumers
::error file=src/models/user.ts,line=1,col=1::circular_dep: user.ts → session.ts → user.ts

P0 findings render as errors (red). P1 findings render as warnings (yellow). The step exits non-zero on P0 findings when --fail-on-p0 is set, blocking the merge.

Expected output in the Actions log:

canopy ci: indexing complete (1,247 files, 4.2s)
canopy ci: running health checks...
FINDINGS:
P0 (critical): 1
broken_import: src/payments/checkout.ts:45 — cannot resolve '../lib/stripe-client'
P1 (error): 2
dead_export: src/utils/format.ts:12 — 'formatCurrency' has 0 consumers
dead_export: src/auth/tokens.ts:88 — 'legacyTokenFormat' has 0 consumers
P2 (warning): 0
canopy ci: FAILED — 1 P0 finding. Fix broken imports before merging.
Error: Process completed with exit code 1.

For larger repos, separate the indexing and health check steps so you can cache the index:

jobs:
forge-index:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Canopy
run: |
curl -fsSL https://downloads.canopy.ironpinelabs.com/releases/latest/forge-linux-x86_64 \
-o canopy && chmod +x canopy && sudo mv canopy /usr/local/bin/canopy
- name: Cache Canopy index
uses: actions/cache@v4
with:
path: .canopy/index
key: forge-${{ runner.os }}-${{ hashFiles('**/*.ts', '**/*.js', '**/*.py', '**/*.rs') }}
restore-keys: forge-${{ runner.os }}-
- name: Activate and index
run: |
canopy activate ${{ secrets.CANOPY_LICENSE_KEY }}
canopy index . --with-search --with-git
forge-health:
needs: forge-index
runs-on: ubuntu-latest
permissions:
pull-requests: write
checks: write
steps:
- uses: actions/checkout@v4
- name: Restore Canopy index
uses: actions/cache@v4
with:
path: .canopy/index
key: forge-${{ runner.os }}-${{ hashFiles('**/*.ts', '**/*.js', '**/*.py', '**/*.rs') }}
- name: Install Canopy and run CI check
run: |
curl -fsSL https://downloads.canopy.ironpinelabs.com/releases/latest/forge-linux-x86_64 \
-o canopy && chmod +x canopy && sudo mv canopy /usr/local/bin/canopy
canopy activate ${{ secrets.CANOPY_LICENSE_KEY }}
canopy ci --repo . --format github --fail-on-p0
FlagBehavior
--fail-on-p0Exit 1 if any P0 (critical) findings
--fail-on-p1Exit 1 if any P0 or P1 (error) findings
(none)Always exits 0, findings are informational only

Start with --fail-on-p0 to establish a baseline without blocking every PR. Add --fail-on-p1 once the codebase is clean.

License activation fails in CI The canopy activate command registers the machine fingerprint. CI runners are ephemeral — they get a new fingerprint each run. Use a license tier that allows multiple machine registrations (Pro or Team), or use canopy ci --team <team_id> which authenticates against the team token instead.

PR annotations don’t appear The workflow needs permissions.pull-requests: write and permissions.checks: write. If your org uses a default permissions policy that restricts this, update the workflow permissions block or ask your GitHub org admin.

Index takes too long Large repos can take 30–90s to index on a fresh CI runner. Use the cache approach above, or upgrade to Team tier for remote cache support.