GitHub Actions¶
Badge¶
Show the latest master status in your README.md:
[](https://github.com/YOUR_ORG/YOUR_REPO/actions/workflows/crap.yml)
The badge reflects the workflow result on master. Make sure the workflow triggers on that branch:
Fail on high CRAP scores¶
name: crap
on:
push:
branches: [main, master]
pull_request:
jobs:
crap:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.23'
cache: true
- name: Install go-crap
run: go install github.com/padiazg/go-crap@latest
- name: Run go-crap
run: go-crap scan --fail-above --threshold 30 --exclude '.*_test\.go' --exclude 'testdata/.*\.go' --exclude '\.pb\.go$'
PR annotations with JSON report¶
- name: Install go-crap
run: go install github.com/padiazg/go-crap@latest
- name: Run go-crap with annotations
run: go-crap scan --format github --threshold 30 --exclude '.*_test\.go'
- name: Generate JSON report
run: go-crap scan --format json > crap-report.json
- uses: actions/upload-artifact@v4
with:
name: crap-report
path: crap-report.json
Matrix builds across Go versions¶
name: crap
on: [push, pull_request]
jobs:
crap:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['1.22', '1.23', '1.24']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache: true
- name: Install go-crap
run: go install github.com/padiazg/go-crap@latest
- name: Run go-crap
run: go-crap scan --fail-above --threshold 30 --exclude '.*_test\.go'
SARIF report with code scanning¶
- name: Run go-crap with SARIF output
run: go-crap scan --format sarif --threshold 30 --exclude '.*_test\.go' > report.sarif
- name: Upload SARIF report
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: report.sarif
SARIF output is compatible with GitHub Advanced Security code scanning, Azure DevOps, and other tools that consume SARIF 2.1.0 reports.
PR comment with CRAP report¶
- name: Run go-crap for PR comment
run: go-crap scan --format pr-comment --threshold 30 --exclude '.*_test\.go' --output pr-comment.md
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const comment = fs.readFileSync('pr-comment.md', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
The pr-comment format generates a markdown table suitable for pull request comments, showing status symbols, CRAP scores, complexity, coverage, function names, and file locations.
Fork-safe PR comment with mutation testing¶
When accepting PRs from forks, the CI job's GITHUB_TOKEN is read-only and cannot post comments. The solution is a two-workflow pattern:
- Workflow 1 (
crap.yaml) -- runs gremlins + go-crap, generates a PR comment as a markdown file, and uploads it as an artifact. - Workflow 2 (
post-pr-comment.yaml) -- triggered byworkflow_run, runs in the base repo context with write permissions, downloads the artifact, and posts (or updates) the comment.
Workflow 1: generate the comment¶
name: crap
on:
push:
branches: [main, master]
pull_request:
jobs:
threshold:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.23'
cache: true
- name: Install go-crap
run: go install github.com/padiazg/go-crap@latest
- name: Score
run: go-crap scan --fail-above --threshold 30 --exclude '.*_test\.go'
pr-comment:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.23'
cache: true
- name: Install gremlins
run: go install github.com/go-gremlins/gremlins/cmd/gremlins@latest
- name: Mutation testing
run: >-
gremlins unleash
--timeout-coefficient 20
-S "l"
--integration
--output=mutation-report.json
- name: Install go-crap
run: go install github.com/padiazg/go-crap@latest
- name: Generate PR comment
run: >-
go-crap scan
--format pr-comment
--threshold 30
--exclude '.*_test\.go'
--output pr-comment.md
--mutation-report mutation-report.json || true
- name: Get PR number
run: echo "${{ github.event.pull_request.number }}" > pr-number.txt
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: crap-comment
path: |
pr-comment.md
pr-number.txt
mutation-report.json
if-no-files-found: ignore
The || true on the go-crap step ensures the comment artifact is still uploaded even when functions exceed the threshold.
See Recommended configuration for an explanation of the gremlins flags.
Workflow 2: post the comment¶
This workflow triggers after crap completes. It runs in the base repo context, so GITHUB_TOKEN has write access to pull requests -- even on fork PRs.
name: post pr comment
on:
workflow_run:
workflows: [crap]
types: [completed]
permissions:
pull-requests: write
actions: read
jobs:
comment:
name: Post or update PR comment
runs-on: ubuntu-latest
steps:
- name: Download PR comment artifact
uses: actions/download-artifact@v4
with:
name: crap-comment
path: .
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
continue-on-error: true
- name: Post or update PR comment
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
if (!fs.existsSync('pr-comment.md') || !fs.existsSync('pr-number.txt')) return;
const body = fs.readFileSync('pr-comment.md', 'utf8');
const prNumber = parseInt(fs.readFileSync('pr-number.txt', 'utf8').trim(), 10);
if (!prNumber) return;
const marker = '<!-- go-crap-report -->';
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const existing = comments.find(c => c.body.startsWith(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}
The <!-- go-crap-report --> marker (emitted by --format pr-comment) ensures the comment is updated in place on subsequent pushes instead of creating duplicates.
Example output¶
Before fixing -- functions above the threshold with unreliable coverage from survived mutants:

After fixing -- all functions below the threshold, no survived mutants:
