Using Scrubby in CI
Run Scrubby's changeset analysis in your CI pipeline as a guardrail before code reaches PR review.
The GitHub App handles automatic PR review, but some teams want Scrubby’s changeset analysis to fail CI early — before the PR even renders. This guide shows how to add Scrubby as a CI step that calls the changeset analysis endpoint and exits non-zero if it finds issues at or above a severity threshold.
When to use this
- You want PR review failures to block CI and prevent merge automatically.
- You want a Scrubby check tied to commit-level CI (e.g. on every push to a branch, not just on PR open).
- You have a non-GitHub deployment and can’t use the GitHub App’s check runs.
If you’re already using the GitHub App and have its check as a required check, you probably don’t need a CI step on top — the GitHub App is the canonical surface.
What you’ll need
- A Scrubby API token. Generate one from your Account page.
- The repository indexed in Scrubby (otherwise CI just blocks on
repo_not_indexed).
The basic shape
Hit the changeset analysis endpoint with the list of changed file paths and your token:
curl -s -X POST \
-H "Authorization: Bearer $SCRUBBY_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n --argjson files "$CHANGED_FILES" '{file_paths: $files}')" \
"https://scrubby.ai/v1/repositories/$SCRUBBY_REPO_ID/review_changeset"
The response is a JSON object with a findings array. Filter by severity, count the matches, and exit accordingly.
Example: GitHub Actions
name: Scrubby Review
on: pull_request
jobs:
scrubby:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # so we can diff against base
- name: Compute changed files
id: diff
run: |
CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | jq -R -s 'split("\n") | map(select(length > 0))')
echo "files=$CHANGED" >> "$GITHUB_OUTPUT"
- name: Run Scrubby changeset review
env:
SCRUBBY_TOKEN: ${{ secrets.SCRUBBY_TOKEN }}
SCRUBBY_REPO_ID: ${{ vars.SCRUBBY_REPO_ID }}
run: |
RESPONSE=$(curl -fsS -X POST \
-H "Authorization: Bearer $SCRUBBY_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"file_paths\": ${{ steps.diff.outputs.files }} }" \
"https://scrubby.ai/v1/repositories/$SCRUBBY_REPO_ID/review_changeset")
echo "$RESPONSE" | jq .
ERRORS=$(echo "$RESPONSE" | jq '[.findings[] | select(.severity == "error")] | length')
if [ "$ERRORS" -gt 0 ]; then
echo "::error::Scrubby found $ERRORS error-severity findings"
exit 1
fi
Severity gating
The example above fails CI on any error-severity finding. To gate on warning or higher:
jq '[.findings[] | select(.severity == "error" or .severity == "warning")] | length'
For most teams, gating on error only is the right choice — warnings should appear as PR feedback but not block the pipeline.
Performance considerations
- Scrubby’s changeset analysis takes a few seconds for typical PRs and up to ~30 seconds for very large ones.
- The endpoint is rate-limited — see Rate Limits. Routine PR-driven CI is well under the limit; mass-running on every commit can hit it on busy repos.
Other CI providers
The shape is the same for GitLab CI, CircleCI, Jenkins, etc.: compute the diff, POST to review_changeset, gate on severity. Adapt the YAML to your provider.
Troubleshooting
repo_not_found— checkSCRUBBY_REPO_IDmatches a repo your token can access.repo_not_indexed— index the repo first via the dashboard or MCP tools.401 Unauthorized— token expired or revoked. Regenerate from the Account page.- No findings ever, even when expected — double-check
file_pathsis reaching the API as a JSON array, not a comma-separated string.
For broader debugging see PR Review Didn’t Run.
Last updated