GitHub Actions Integration
import { Aside, Tabs, TabItem } from ‘@astrojs/starlight/components’;
Running forge 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”- Forge license key (Solo or Pro —
forge ciis not available in Community tier) - License key stored as a GitHub Actions secret named
FORGE_LICENSE_KEY - A repo with a Forge 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: FORGE_LICENSE_KEY
Value: your Forge license key
Basic workflow
Section titled “Basic workflow”Create .github/workflows/forge.yml:
name: Forge 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 Forge run: | curl -fsSL https://downloads.forge.ironpinelabs.com/releases/latest/forge-linux-x86_64 \ -o forge chmod +x forge sudo mv forge /usr/local/bin/forge forge --version
- name: Activate license run: forge activate ${{ secrets.FORGE_LICENSE_KEY }} env: CI: true
- name: Index repo run: forge index . --with-search --with-git
- name: Run health check run: forge ci --repo . --format github --fail-on-p0What forge ci outputs
Section titled “What forge ci outputs”With --format github, Forge 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:
forge ci: indexing complete (1,247 files, 4.2s)forge 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
forge 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 Forge run: | curl -fsSL https://downloads.forge.ironpinelabs.com/releases/latest/forge-linux-x86_64 \ -o forge && chmod +x forge && sudo mv forge /usr/local/bin/forge
- name: Cache Forge index uses: actions/cache@v4 with: path: .forge/index key: forge-${{ runner.os }}-${{ hashFiles('**/*.ts', '**/*.js', '**/*.py', '**/*.rs') }} restore-keys: forge-${{ runner.os }}-
- name: Activate and index run: | forge activate ${{ secrets.FORGE_LICENSE_KEY }} forge 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 Forge index uses: actions/cache@v4 with: path: .forge/index key: forge-${{ runner.os }}-${{ hashFiles('**/*.ts', '**/*.js', '**/*.py', '**/*.rs') }}
- name: Install Forge and run CI check run: | curl -fsSL https://downloads.forge.ironpinelabs.com/releases/latest/forge-linux-x86_64 \ -o forge && chmod +x forge && sudo mv forge /usr/local/bin/forge forge activate ${{ secrets.FORGE_LICENSE_KEY }} forge 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 forge 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 forge 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.