CI integration recipes
Drop-in workflows for the major CI systems. Each recipe runs cofferdam check, gates the build on --fail-on=<level>, and uses the project's baseline (when present) so adopting cofferdam on legacy code doesn't immediately turn CI red.
Recipe selector
| You want… | Use |
|---|---|
| Plain "fail the build on new findings" | GitHub Actions §1 — applies to every system |
| PR-only review (only check changed files) | §2 PR-only mode |
| Baseline-tracked rollout on a legacy repo | §3 Baseline pattern |
| AI-agent-friendly output for review bots | §4 Robot mode |
| Faster builds via caching | §5 Caching |
| Findings on the PR + Security tab via SARIF | §6 SARIF upload |
Each recipe is shown for GitHub Actions first because it's the most common; GitLab / CircleCI / Drone equivalents are at the bottom.
Universal flags
A short cheat sheet for the flags that matter in CI. Full reference: docs/output-formats.md, docs/checks/, or cofferdam check --help.
| Flag | Purpose |
|---|---|
--fail-on=<level> | Severity threshold for exit-1 gate. info / low / medium / high / critical. Default medium. |
--baseline=<path> | Active baseline file. Auto-detected at .cofferdam/baseline.json when present. |
--no-baseline | Disable baseline detection entirely for this run. |
--since=<git-ref> | PR-only mode — only check files changed in <git-ref>...HEAD. |
--robot | Default to a machine-readable format. Pairs with --format=compact for AI shovelling. |
--format=<text|json|compact|sarif> | Output format. text for humans, json for tools, compact for AI agents, sarif for GitHub Code Scanning + other static-analysis consumers. |
--max-issues=<N> | Cap rendered findings (gate still uses the full set). |
--quiet | Suppress info lines (decorative output, not findings). |
GitHub Actions
1. Minimal: fail the build on medium-or-higher
# .github/workflows/cofferdam.yml
name: cofferdam
on:
push:
branches: [main]
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npx --yes @cofferdam/cofferdam checknpx --yes @cofferdam/cofferdam check walks the current directory and exits 1 on any finding at medium severity or higher. No config required. Baselined findings (if .cofferdam/baseline.json is committed) never trigger the gate.
2. PR-only mode with --since
Only check files changed in the PR. Safe to drop into a noisy legacy repo: pre-existing findings on untouched files are ignored entirely.
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # cofferdam needs full history to diff against the base
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npx --yes @cofferdam/cofferdam check --since=origin/${{ github.base_ref }}
if: github.event_name == 'pull_request'
- run: npx --yes @cofferdam/cofferdam check
if: github.event_name == 'push'The if: guards split the job into two paths: PRs check only the diff against the base branch; pushes to main check the full repo.
3. Baseline pattern: only fail on new findings
For repos adopting cofferdam without a clean rewrite. Capture once, commit, then CI fails only on findings absent from the baseline.
One-time setup (locally):
npx @cofferdam/cofferdam init --baseline # writes cofferdam.toml + .cofferdam/baseline.json
git add cofferdam.toml .cofferdam/baseline.json
git commit -m "chore: adopt cofferdam with baseline"CI workflow is unchanged from §1 — cofferdam check auto-detects .cofferdam/baseline.json and treats baselined findings as non-gating. Pass --fail-on-new if you want to make the intent explicit in CI logs:
- run: npx --yes @cofferdam/cofferdam check --fail-on-newWhen you fix a baselined finding, regenerate:
npx @cofferdam/cofferdam baseline write
git commit -am "chore(cofferdam): refresh baseline after fixing X"4. Robot mode for AI review bots
Pipe --robot --format=compact output into an AI review step. Compact format is line-delimited (splitn(8, '|')-friendly) and ~44% smaller than JSON.
- name: Run cofferdam (compact)
id: cofferdam
run: |
npx --yes @cofferdam/cofferdam check --robot --format=compact > findings.txt
echo "count=$(tail -n +2 findings.txt | wc -l)" >> "$GITHUB_OUTPUT"
continue-on-error: true
- name: Render to step summary
if: always()
run: |
{
echo "## Cofferdam — ${{ steps.cofferdam.outputs.count }} finding(s)"
echo
echo '```'
cat findings.txt
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
- name: Fail if findings
if: steps.cofferdam.outputs.count != '0'
run: exit 1continue-on-error lets the step summary render even when the gate would normally fail the run; the explicit exit 1 step at the end re-applies the gate. The header line counts as line 1, so tail -n +2 skips it.
For full-fidelity JSON (with baseline tags, related spans, truncation metadata):
- run: npx --yes @cofferdam/cofferdam check --robot --format=json > findings.json5. Caching
cofferdam ships as an npm package that downloads a Rust binary on postinstall. Cache ~/.npm and the global cofferdam install to skip the download on subsequent runs.
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm # caches ~/.npm by lockfile hashIf your project already has a package.json and package-lock.json with cofferdam as a devDependency, the standard npm ci cache key handles cofferdam's binary too — no extra config needed.
6. SARIF upload to GitHub Code Scanning
Emit SARIF 2.1.0 and let GitHub render findings on the PR diff and in the Security → Code scanning tab. No bespoke parsing — github/codeql-action/upload-sarif consumes the format directly.
# .github/workflows/cofferdam-sarif.yml
name: cofferdam (SARIF)
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
security-events: write # required for upload-sarif
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Run cofferdam (SARIF)
run: npx --yes @cofferdam/cofferdam check --format=sarif > cofferdam.sarif
continue-on-error: true # don't block the upload on a non-zero exit
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: cofferdam.sarif
category: cofferdam # distinguishes from CodeQL or other SARIF uploadsAfter the workflow runs once on main, findings show up:
- Pull-request files view — annotated inline next to the offending lines.
- Security tab → Code scanning alerts — full list with state (open / fixed / dismissed) tracked across runs via cofferdam's
partialFingerprints.
Notes:
permissions.security-events: writeis mandatory; without it the upload step 403s.continue-on-error: trueon the cofferdam step lets the SARIF upload happen even when--fail-on=<level>would otherwise exit 1. The findings still drive whatever PR-required-status checks you wire into the Security policies.category: cofferdamlets you run cofferdam alongside CodeQL or other SARIF emitters without findings overwriting each other.- For private repos, GitHub Code Scanning requires GitHub Advanced Security; public repos get it for free.
If you want the gate exit code to also fail the workflow (in addition to driving alerts), drop continue-on-error: true and accept that the SARIF upload step won't run on red builds — or add a separate cofferdam check step (no --format=sarif) before the SARIF emitter and let the upload step run with if: always().
GitLab CI
# .gitlab-ci.yml
cofferdam:
image: node:20
script:
- npx --yes @cofferdam/cofferdam check
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
variables:
BASE: $CI_MERGE_REQUEST_DIFF_BASE_SHA
changes:
- "**/*.{ts,tsx,mts,cts}"
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
- .npm/PR-only mode:
script:
- npx --yes @cofferdam/cofferdam check --since=$CI_MERGE_REQUEST_DIFF_BASE_SHAGitLab gives you the merge-request base SHA in $CI_MERGE_REQUEST_DIFF_BASE_SHA. Outside MR pipelines (push to default branch), drop the flag to scan the full repo.
Robot mode → MR comment via the GitLab API: capture compact output to a file, then post via curl from the same job. Pattern is the same as the GitHub step-summary recipe.
CircleCI
# .circleci/config.yml
version: 2.1
jobs:
cofferdam:
docker:
- image: cimg/node:20.0
steps:
- checkout
- restore_cache:
keys:
- cofferdam-v1-{{ checksum "package-lock.json" }}
- run: npm ci
- run: npx @cofferdam/cofferdam check
- save_cache:
key: cofferdam-v1-{{ checksum "package-lock.json" }}
paths:
- ~/.npm
- node_modules
workflows:
check:
jobs:
- cofferdamPR-only: replace npx @cofferdam/cofferdam check with npx @cofferdam/cofferdam check --since=origin/main (or origin/$CIRCLE_BRANCH when you have it).
Drone / Woodpecker / generic
Any CI that runs a Linux container with node:20 works. Minimal:
# .drone.yml or .woodpecker.yml
steps:
cofferdam:
image: node:20
commands:
- npx --yes @cofferdam/cofferdam checkPre-commit hook
Run cofferdam locally before each commit. Two options:
Husky + lint-staged
// package.json
{
"lint-staged": {
"*.{ts,tsx,mts,cts}": "cofferdam check --no-baseline --quiet"
}
}lint-staged appends the staged file paths to the command, so cofferdam runs against exactly the files about to be committed — no need for --since. --no-baseline is recommended at the pre-commit stage so legacy baselined findings don't quietly slip through. --quiet keeps the output terse.
Plain pre-commit hook
#!/usr/bin/env bash
# .git/hooks/pre-commit
set -e
files=$(git diff --cached --name-only --diff-filter=AMR -- '*.ts' '*.tsx' '*.mts' '*.cts')
[ -z "$files" ] && exit 0
echo "$files" | xargs npx @cofferdam/cofferdam check --no-baseline --quiet --max-issues=20--max-issues=20 caps the displayed list so a noisy commit doesn't flood the terminal. The gate still considers the full set.
Tuning checklist
Before shipping a CI integration, walk through:
- Severity threshold. Default
--fail-on=medium. Demote tolowwhen you want CI to gate on the Refactor / Readability rules; promote tohighif you only want the security-tier (Warning.NoEval,Warning.TripleEquals) to break the build. - Baseline. Capture once on a legacy adoption (
cofferdam init --baseline), then refresh after each cleanup run (cofferdam baseline write). - PR-only vs. full-repo. PR-only (
--since) for fast feedback on contributors; full-repo on the default branch to catch regressions on shared code paths. - Output format.
textfor human terminal logs,jsonfor tooling integrations (PR-comment bots, dashboards),compactwhen the consumer is an LLM that pays per token,sariffor GitHub Code Scanning + other static-analysis consumers. - Caching.
actions/setup-node@v4 with: cache: npm(or equivalent) handles the postinstall binary download for free.
Pre-flight checks
Before wiring up a new CI pipeline, run cofferdam doctor --robot as a fast pre-flight step to verify the binary, config, baseline, and git integration are all in good shape. A non-zero exit from doctor means something is misconfigured that will cause cofferdam check to behave unexpectedly.
- name: Pre-flight
run: npx --yes @cofferdam/cofferdam doctor --robotdoctor exits 0 on warns (so a missing baseline or unknown config key doesn't break CI) and exits 1 only when a hard failure is detected (missing git, corrupt baseline, binary version mismatch).
What's not yet here
- Self-hosted runner notes — cofferdam's npm postinstall downloads a Rust binary; air-gapped runners need either a local mirror or a
cargo installfrom source. Detailed recipe is a follow-up bead.