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.
Prerequisites
Section titled “Prerequisites”- Canopy license key (Solo or Pro —
canopy ciis 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)
Add the secret
Section titled “Add the secret”In your GitHub repo: Settings → Secrets and variables → Actions → New repository secret.
Name: CANOPY_LICENSE_KEY
Value: your Canopy license key
Basic workflow
Section titled “Basic workflow”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-p0What canopy ci outputs
Section titled “What canopy ci outputs”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.tsP0 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.Separate jobs for health vs. CI gate
Section titled “Separate jobs for health vs. CI gate”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-p0Fail conditions
Section titled “Fail conditions”| Flag | Behavior |
|---|---|
--fail-on-p0 | Exit 1 if any P0 (critical) findings |
--fail-on-p1 | Exit 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.
Common pitfalls
Section titled “Common pitfalls”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.