GitLab CI Integration
import { Aside, Tabs, TabItem } from ‘@astrojs/starlight/components’;
GitLab CI can display code quality findings as a report widget on merge requests. forge ci --format gitlab produces the Code Quality JSON format that GitLab expects. This guide walks through the complete pipeline setup.
Prerequisites
Section titled “Prerequisites”- Forge license key (Solo or Pro —
forge ciis not available in Community tier) - License key stored as a GitLab CI/CD variable named
FORGE_LICENSE_KEY(masked, not protected — it needs to be available on non-protected branches) - GitLab 15.0+ (Code Quality report widgets were stabilized in 15.0)
Add the CI/CD variable
Section titled “Add the CI/CD variable”In your GitLab project: Settings → CI/CD → Variables → Add variable.
Key: FORGE_LICENSE_KEY
Value: your Forge license key
Type: Variable
Flags: Masked, Expand variable reference disabled
Basic .gitlab-ci.yml
Section titled “Basic .gitlab-ci.yml”stages: - quality
forge-health: stage: quality image: ubuntu:22.04 before_script: - apt-get update -qq && apt-get install -y -qq curl - curl -fsSL https://downloads.forge.ironpinelabs.com/releases/latest/forge-linux-x86_64 -o forge - chmod +x forge - mv forge /usr/local/bin/forge - forge --version - forge activate $FORGE_LICENSE_KEY
script: - forge index . --with-search --with-git - forge ci --repo . --format gitlab --fail-on-p0 > forge-report.json || true # Capture exit code separately so the artifact upload always runs - forge ci --repo . --format gitlab --fail-on-p0
artifacts: reports: codequality: forge-report.json when: always expire_in: 1 week
rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHCleaner approach with separate commands
Section titled “Cleaner approach with separate commands”A cleaner pattern using shell exit code capture:
forge-health: stage: quality image: ubuntu:22.04 before_script: - apt-get update -qq && apt-get install -y -qq curl - curl -fsSL https://downloads.forge.ironpinelabs.com/releases/latest/forge-linux-x86_64 -o /usr/local/bin/forge - chmod +x /usr/local/bin/forge - forge activate $FORGE_LICENSE_KEY
script: - forge index . --with-search --with-git - forge ci --repo . --format gitlab --fail-on-p0 | tee forge-report.json - exit ${PIPESTATUS[0]}
artifacts: reports: codequality: forge-report.json when: always expire_in: 1 weektee writes the JSON to forge-report.json while still passing it to stdout. ${PIPESTATUS[0]} captures the exit code of forge ci (not tee) so the job fails correctly on P0 findings.
What the report looks like
Section titled “What the report looks like”forge ci --format gitlab produces GitLab’s Code Quality JSON schema:
[ { "description": "broken_import: cannot resolve '../lib/stripe-client'", "fingerprint": "a1b2c3d4e5f6...", "severity": "blocker", "location": { "path": "src/payments/checkout.ts", "lines": { "begin": 45 } } }, { "description": "dead_export: 'formatCurrency' has 0 consumers", "fingerprint": "b2c3d4e5f6a1...", "severity": "minor", "location": { "path": "src/utils/format.ts", "lines": { "begin": 12 } } }]GitLab severity mapping:
| Forge severity | GitLab severity |
|---|---|
| P0 (critical) | blocker |
| P1 (error) | major |
| P2 (warning) | minor |
| P3 (info) | info |
Caching the index
Section titled “Caching the index”Add a GitLab cache block to avoid re-indexing on every pipeline run:
forge-health: stage: quality image: ubuntu:22.04 cache: key: forge-$CI_COMMIT_REF_SLUG paths: - .forge/index/ policy: pull-push
before_script: - apt-get update -qq && apt-get install -y -qq curl - curl -fsSL https://downloads.forge.ironpinelabs.com/releases/latest/forge-linux-x86_64 -o /usr/local/bin/forge - chmod +x /usr/local/bin/forge - forge activate $FORGE_LICENSE_KEY
script: - forge index . --with-search --with-git # incremental if cache hit - forge ci --repo . --format gitlab --fail-on-p0 | tee forge-report.json - exit ${PIPESTATUS[0]}
artifacts: reports: codequality: forge-report.json when: alwaysforge index is incremental by default — on a cache hit, it only re-indexes changed files.
Common pitfalls
Section titled “Common pitfalls”Report widget doesn’t appear on MR
The artifacts.reports.codequality path must match the file written by the script. Double-check the filename is forge-report.json and the artifact path is identical.
Job always passes even with P0 findings
Check that you’re not swallowing the exit code. With a pipe (| tee), use ${PIPESTATUS[0]}. With || true, the job will never fail — remove it from the enforcing command.
Variable not available on feature branches
Make sure FORGE_LICENSE_KEY is not set as a “protected variable” in GitLab. Protected variables are only available on protected branches; if your MR branches are not protected, the variable won’t be injected.