Skip to content

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.

  • Forge license key (Solo or Pro — forge ci is 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)

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

Name: FORGE_LICENSE_KEY
Value: your Forge license key

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-p0

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.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:

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.

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-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 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.