Scrubby

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 — check SCRUBBY_REPO_ID matches 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_paths is reaching the API as a JSON array, not a comma-separated string.

For broader debugging see PR Review Didn’t Run.

Last updated