Skip to content

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.

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

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

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_BRANCH

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 week

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

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 severityGitLab severity
P0 (critical)blocker
P1 (error)major
P2 (warning)minor
P3 (info)info

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

forge index is incremental by default — on a cache hit, it only re-indexes changed files.

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.