CI/CD quality gate — fail the build on compliance regression
For: all
Tier: pro+
Time: ~5 min
Why you'd do this
Catching a compliance regression at PR-review time is much cheaper than catching it at audit time. The Quality Gate is the policy enforcement counterpart to SARIF export: instead of merely showing findings in the Security tab, the gate FAILS the CI run when the configured thresholds are exceeded — so non-compliant PRs cannot merge.
Before you start
- Pro tier or higher (
ciGateflag intiers.ts). Free / Starter see the upgrade prompt on the/ci-cdpage - An API key from Settings → API Keys
- GitHub Actions runner — the gate is currently GitHub Actions only; GitLab CI / CircleCI templates land in a future release
- A baseline understanding of your repo's current compliance score (visit the Repo Overview to see today's count of NON_COMPLIANT findings)
Step 1
The minimal gate workflow
The gate is a single CLI invocation that exits non-zero when compliance regresses against your configured threshold. Drop this file at .github/workflows/compliance-gate.yml:
name: Compliance Gate
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Run ComplianceLint gate
env:
CL_API_KEY: ${{ secrets.CL_API_KEY }}
run: npx -y @compliancelint/gate@latest --max-non-compliant 0
Default behaviour: the gate fetches the latest scan for the repo (matched via git remote get-url origin), counts NON_COMPLIANT findings, and exits 1 if the count exceeds the configured threshold.
Step 2
Threshold knobs
Pick the level of strictness that matches your team's risk appetite:
| Flag | Effect | When to use |
|---|---|---|
| --max-non-compliant N | Fail if NON_COMPLIANT count > N | Strict — typically 0 for high-risk systems near launch |
| --max-non-compliant-delta N | Fail only if NEW NON_COMPLIANT findings appeared on this PR (vs main baseline) | Looser — lets existing debt persist while preventing regression |
| --articles 9,12,14,15 | Apply gate to only these articles | Prioritise the riskiest articles first; widen as your evidence backlog clears |
| --allow-utd | Don't count UNABLE_TO_DETERMINE as a failure | Useful early in the obligation-decomposition lifecycle when AI confidence is low |
| --require-evidence-for ART09-OBL-* | Fail unless every matching obligation has evidence_provided | Use when you've decided certain articles must always have explicit evidence |
Step 3
Recommended progressive rollout
Day 1 of adopting the gate: set --max-non-compliant-delta 0. This freezes your current debt without creating a wave of broken builds, and forces every new PR to either fix or justify any NEW finding it introduces.
Week 2-4: pick the riskiest 2-3 articles (typically Art. 9 risk management + Art. 13 transparency + Art. 14 human oversight) and switch to --max-non-compliant 0 --articles 9,13,14. Force fully-clean state on those, accept debt elsewhere.
Month 2+: expand --articles set as your evidence backlog clears. Eventually drop the article filter entirely once the full obligation set is at zero NON_COMPLIANT.
Step 4
PR comment feedback
Add --pr-comment to post a Markdown summary as a sticky PR comment (updates on each push, doesn't spam):
- name: Run ComplianceLint gate
env:
CL_API_KEY: ${{ secrets.CL_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
npx -y @compliancelint/gate@latest \
--max-non-compliant-delta 0 \
--pr-comment
The comment lists each new NON_COMPLIANT finding with its obligation id + a deep-link to the dashboard view + the obligation's human_judgment_needed field as remediation hint.
What can go wrong
- Gate fails on every PR with
no scan found for repo origin— The gate matches bygit remote get-url origin. Either: (a) your CI's checkout doesn't preserve the remote — addwith: fetch-depth: 0to actions/checkout; (b) the dashboard's repo row uses a different URL form — check Repo Settings on the dashboard and update the remote URL in your repo to match. - Gate counts the same finding twice across re-scans — Findings are de-duplicated by
(obligation_id, scan_id)on the dashboard side. If you see double-counting, your CI is probably triggering two scans concurrently and the gate is querying mid-flight. Add aconcurrency: { group: cl-gate-${{ github.ref }} }block to serialise. - Team needs a temporary bypass to ship an urgent hotfix — Use a
[skip-cl-gate]token in the commit message — the gate skips when present and posts a stickied comment noting the bypass + who triggered it (audit trail). Alternative: use branch protection's admin override for one-off releases.
Related
Last updated: 2026-04-30