Reference
CrawlCheck in CI: fail the pull request that breaks the machine layer
One workflow step. It scans the host the PR deploys to as every named AI crawler, prints the grade and every finding into the job log, and fails the job when any finding is above the severity you allow. A refusal fails too, because a wall is not a pass.
The workflow
Add CRAWLCHECK_KEY to the repository secrets (an Agency licence key: the keyed lane allows 60 scans per ten minutes). Then:
name: CrawlCheck
on:
pull_request:
workflow_dispatch:
jobs:
crawlcheck:
runs-on: ubuntu-latest
steps:
- name: Scan the staging site as every AI crawler
env:
CRAWLCHECK_KEY: ${{ secrets.CRAWLCHECK_KEY }}
DOMAIN: staging.example.com # the host this PR deploys to
MAX_SEVERITY: 3 # fail on 4 (high) or 5 (critical)
run: |
set -e
R=$(curl -sS -X POST https://crawlcheck.io/api/scan/licensed \
-H "content-type: application/json" -H "x-crawlcheck-key: $CRAWLCHECK_KEY" \
-d "{\"domain\":\"$DOMAIN\"}")
echo "$R" | jq '{grade, overall, findings: [.findings[] | {code, severity, path}]}'
WORST=$(echo "$R" | jq '[.findings[].severity] | max // 0')
REFUSED=$(echo "$R" | jq '.refused // false')
if [ "$REFUSED" = "true" ]; then echo "::error::the staging host refused CrawlCheck - nothing was measured"; exit 1; fi
if [ "$WORST" -gt "$MAX_SEVERITY" ]; then echo "::error::a finding of severity $WORST is above the threshold $MAX_SEVERITY"; exit 1; fi
echo "::notice::CrawlCheck grade $(echo "$R" | jq -r .grade) $(echo "$R" | jq -r .overall), worst finding severity $WORST"What it catches
Crawler permissions
A robots.txt edit that shadows a rule, disallows the sitemap, or opts out of an answer engine nobody meant to block.
The entity graph
A template change that drops the JSON-LD block, duplicates an @id, or breaks the sameAs corroboration.
Edge behaviour
A firewall rule that starts refusing GPTBot or ClaudeBot while browsers still get 200, or a cache rule that pins a challenge page on a machine file.
Payload
A build that inlines the stylesheet again and drops the page under 5% visible text.
Reading the result
The JSON is the same record the report renders from. findings[].severity runs 1 (note) to 5 (critical); the grade letter is capped by the worst finding. refused: true means the host answered CrawlCheck with a wall, which is not a measurement and must not be read as clean.
Vercel, Netlify and any other CI run the same curl. The scan is the same one the free form runs; the key only buys the lane and the record.