Quality gates in CI
Use the sitecmd CLI to fail a build when site health regresses. Examples for GitHub Actions, GitLab CI, CircleCI, and more.
A quality gate is a check that fails your build if your site is in worse shape than you’ll accept. SiteCMD’s CLI gives you a simple, exit-code-driven way to do this from any CI system.
This page is the recipe collection. For the underlying CLI behavior, see CLI reference.
The basic gate
sitecmd init https://staging.example.com --yes
sitecmd scan --fail-under 85
That’s the entire pattern. init writes a .sitecmd/config.json if one isn’t there. scan runs the checks. If the score is below 85, the command exits with code 1 and the CI job fails. If the score is 85 or above, it exits 0 and the job passes.
Most teams start here and iterate.
Picking a threshold
Some defaults that work for most projects:
- Marketing site, low-stakes:
--fail-under 75. Catches bad regressions, doesn’t block on polish. - Production product site:
--fail-under 85. Reasonable bar for a launched site you care about. - High-stakes (financial, health, anything regulated):
--fail-under 90and add--type security --fail-under 95as a second gate.
You can also pin a category-specific gate. sitecmd scan --type security --fail-under 95 only checks security category, and only fails if security drops below 95.
Multi-gate strategy
Run the same scan multiple times with different filters when you want different bars for different categories:
sitecmd scan --type security --fail-under 95
sitecmd scan --type accessibility --fail-under 80
sitecmd scan --fail-under 75
Each line is its own gate. Any one failing fails the build.
GitHub Actions
name: Site health
on:
push:
branches: [main]
pull_request:
jobs:
sitecmd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install SiteCMD CLI
run: |
# at launch: install via package manager or direct binary download
curl -fsSL https://sitecmd.com/install.sh | sh
- name: Scan staging
run: |
sitecmd init https://staging.example.com --yes
sitecmd scan --fail-under 85
The CLI’s exit code is what GitHub Actions reads. No extra adapter needed.
GitLab CI
sitecmd:
image: sitecmd/cli:latest
script:
- sitecmd init https://staging.example.com --yes
- sitecmd scan --fail-under 85
The Docker image keeps the CI job light.
CircleCI
jobs:
sitecmd:
docker:
- image: sitecmd/cli:latest
steps:
- checkout
- run:
name: Scan
command: |
sitecmd init https://staging.example.com --yes
sitecmd scan --fail-under 85
Pre-deploy vs. post-deploy
Two common shapes:
- Pre-deploy gate: scan staging before promoting to production. Fails the deploy if staging regressed.
- Post-deploy verification: scan production right after deploy. Surfaces regressions immediately, even if they only manifest with real production config (CDN headers, env vars, third-party scripts).
You typically want both, run independently.
deploy:
needs: [pre-deploy-scan]
# ...
pre-deploy-scan:
run: sitecmd scan --url https://staging.example.com --fail-under 85
post-deploy-scan:
needs: [deploy]
run: sitecmd scan --url https://example.com --fail-under 85
Comparing scans (regression-only gates)
If you want to fail on new regressions rather than absolute thresholds, use --diff:
sitecmd scan --diff
This compares against .sitecmd/last-scan.json from the previous run. If new criticals appeared, the exit code reflects that. Useful when you’re improving a site over time and don’t want to block on the legacy baseline.
You’ll need to persist .sitecmd/last-scan.json between CI runs for --diff to work. Most CI tools support cache or artifact-passing for this.
Performance and Core Web Vitals
CWV checks need a headless Chrome and add 5-15 seconds per page. Don’t put them in every PR scan. A typical pattern is a separate nightly job:
nightly-cwv:
schedule: "0 4 * * *" # daily at 4 AM
run: sitecmd scan --cwv --fail-under 85
For PR scans, --no-browser explicitly skips browser-based checks and keeps the run fast:
pr-scan:
run: sitecmd scan --no-browser --fail-under 85
Output for humans
By default, sitecmd scan prints a readable summary to stdout. CI logs are perfectly fine for the day-to-day. If you want structured output for a script or a custom report:
sitecmd scan --json > results.json
Then parse with jq or pipe into whatever you want.
Output for pull request comments
The fix prompts surface is designed for this:
sitecmd fix --all > fixes.md
In GitHub Actions, you can post fixes.md as a PR comment:
- name: Comment fixes on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require("fs");
const body = fs.readFileSync("fixes.md", "utf8");
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body
});
When the gate fails
A failed gate doesn’t tell you what to fix. The CI log shows the headline (“Score 72, below threshold of 85”) but the details live in the scan JSON. Common pattern:
- CI fails the build with the score.
- Developer opens the SiteCMD desktop app (if they have it), or downloads
results.jsonfrom the CI artifacts. - Triage from there.
You can also wire the post-failure step to upload the scan JSON as an artifact:
- name: Upload scan results
if: failure()
uses: actions/upload-artifact@v4
with:
name: sitecmd-scan
path: .sitecmd/last-scan.json
What CI gates can’t do
- They can’t verify fixes. The CLI runs against a URL; it doesn’t know whether your fix in this PR actually shipped to the URL it’s scanning. If your CI scans staging and the PR hasn’t been merged into staging, you’re scanning the previous version of the site.
- They can’t use tier-gated features. Cross-source correlation, fix guides, and ticket mirroring are desktop-app features tied to a license. The CLI runs free-tier checks only.
- They can’t replace a real review. A passing gate means the site cleared the threshold, not that it’s perfect. Use gates as a floor, not a ceiling.