Lint and Static Analysis Findings (Beta)
A per-commit annotation tells you what a linter found on this diff, then disappears with the PR. Gaffer keeps every upload. It tells you which findings are new on a branch, which have sat on main for weeks, and which files collect the most of them.
Supported Tools
Section titled “Supported Tools”Gaffer parses SARIF 2.1.0. Any tool that emits it works. These are the common ones.
| Tool | Command |
|---|---|
| ruff | ruff check --output-format sarif > results.sarif |
| ESLint | eslint -f @microsoft/eslint-formatter-sarif -o results.sarif . |
| golangci-lint (v2) | golangci-lint run --output.sarif.path results.sarif |
| clippy | cargo clippy --message-format=json | clippy-sarif > results.sarif |
ESLint needs npm install --save-dev @microsoft/eslint-formatter-sarif. Clippy needs cargo install clippy-sarif.
Gaffer also reads SARIF from Semgrep, CodeQL, Trivy and Checkov. For example, semgrep scan --sarif --sarif-output=results.sarif, or trivy fs --format sarif -o results.sarif .. Checkov has -o sarif.
Upload
Section titled “Upload”Upload a SARIF file the same way you upload a test report. Both .sarif and .json extensions work.
gaffer upload results.sarifYou can also upload from GitHub Actions or with the Upload API.
GitHub Actions
Section titled “GitHub Actions”Linters exit non-zero when they find something. Keep the upload step running with if: always().
name: Lint
on: [push, pull_request]
jobs: ruff: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Run ruff run: pipx run ruff check --output-format sarif > results.sarif
- name: Install Gaffer CLI if: always() run: curl -fsSL https://app.gaffer.sh/install.sh | sh
- name: Upload to Gaffer if: always() run: > gaffer upload results.sarif --commit-sha ${{ github.event.pull_request.head.sha || github.sha }} --branch ${{ github.head_ref || github.ref_name }} env: GAFFER_PROJECT_TOKEN: ${{ secrets.GAFFER_PROJECT_TOKEN }}Upload API
Section titled “Upload API”curl -X POST https://app.gaffer.sh/api/upload \ -H "X-API-Key: YOUR_PROJECT_TOKEN" \ -F 'tags={"commitSha":"abc123","branch":"main"}'Pass --branch and --commit-sha to the CLI, or the matching tags to the API. The branch comparison below needs them.
What the File Must Contain
Section titled “What the File Must Contain”A SARIF 2.1.0 log with at least one run.
Each result should carry a ruleId and a physical location: a file and a start line.
A finding without a location is stored and counted. It cannot appear in hotspots, and Gaffer cannot match it across uploads.
A finding without partialFingerprints is keyed by rule, file and position.
How Finding Identity Works
Section titled “How Finding Identity Works”Gaffer gives every finding a stable identity so it can recognise the same finding in the next upload.
If the tool writes SARIF partialFingerprints, Gaffer uses them. Otherwise it hashes the rule, the file and the position.
A position hash shifts when lines above the finding move. An edit near the top of a file can make an existing finding show up as new while its previous copy is marked resolved.
Prefer a tool that emits partialFingerprints when you can.
What the Dashboard Shows
Section titled “What the Dashboard Shows”Open the findings views from the project page.
- New on this branch: a PR branch’s findings diffed against the main branch. Pre-existing findings stay separate.
- Chronic: findings that survive a configurable number of consecutive main runs (default 20), grouped by rule and file.
- Hotspots: files ranked by finding count, beside line coverage from the latest coverage report and failing or flaky tests.
Start with the hotspots that are poorly covered and also flaky. Those files hurt twice.