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/canopy.yml:
name: Canopy Health Check
on: pull_request: branches: [main] push: branches: [main]
jobs: canopy-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 $CANOPY_DOWNLOAD_URL/releases/latest/canopy-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 a one-line summary header followed by GitHub Actions workflow commands to stdout. The workflow commands render as inline annotations on the PR; the summary line stays in the raw log so you can read the verdict without expanding the annotations panel:
canopy ci: FAIL — 1 critical, 0 errors, 2 warnings (4187ms) [/home/runner/work/myapp/myapp]::error,file=src/payments/checkout.ts,line=45,title=canopy/broken_imports::cannot resolve '../lib/stripe-client'::warning,file=src/utils/format.ts,line=12,title=canopy/dead_exports::'formatCurrency' has 0 consumers::warning,file=src/auth/tokens.ts,line=88,title=canopy/dead_exports::'legacyTokenFormat' has 0 consumersSeverity mapping: Critical and Error findings emit ::error (red on the PR), Warning emits ::warning (yellow), Info emits ::notice (blue). The step exits non-zero on critical/error findings when --fail-on-p0 is set, blocking the merge.
The annotation title= field is canopy/<check_id> so you can group findings in the GitHub UI by which check produced them. Commas inside file paths are URL-escaped (%2C) and %/newlines inside messages are escaped (%25/%0A) per the GitHub Actions annotation spec.
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: canopy-index: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Canopy run: | curl -fsSL $CANOPY_DOWNLOAD_URL/releases/latest/canopy-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: canopy-${{ runner.os }}-${{ hashFiles('**/*.ts', '**/*.js', '**/*.py', '**/*.rs') }} restore-keys: canopy-${{ runner.os }}-
- name: Activate and index run: | canopy activate ${{ secrets.CANOPY_LICENSE_KEY }} canopy index . --with-search --with-git
canopy-health: needs: canopy-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: canopy-${{ runner.os }}-${{ hashFiles('**/*.ts', '**/*.js', '**/*.py', '**/*.rs') }}
- name: Install Canopy and run CI check run: | curl -fsSL $CANOPY_DOWNLOAD_URL/releases/latest/canopy-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.