SARIF is how Shipmoor findings become first-class citizens of your code-review surface: annotations on the PR diff, entries in the Security tab, and a history of what was found when. Shipmoor emits standard SARIF 2.1.0 from the same evidence as its human-readable and JSON output, so the three views never disagree about what the scan caught.
Producing SARIF
Add --sarif and point --output at a file:
shipmoor scan --diff origin/main...HEAD --sarif --output report.sarif
Every finding carries its rule ID (e.g. python.phantom_import), severity, location, message, and remediation text, plus a stable fingerprint, so code scanning can track a finding across pushes instead of re-opening it as new each time. Rule metadata links back to the rule’s documentation.
When --sarif writes to stdout instead of a file, stdout contains only the SARIF document (diagnostics go to stderr), so it pipes cleanly into another tool.
Suppressions stay in parity. A finding you’ve dismissed with an inline
shipmoor:ignoredirective is removed before the gate runs and before either machine format is written, so the SARIF omits exactly the same suppressed findings the JSON does. The editor, the JSON contract, and code scanning all agree on what was suppressed.
Uploading to GitHub code scanning
Hand the SARIF file to the official upload action:
- name: Run Shipmoor
run: |
"$HOME/.shipmoor/bin/shipmoor" scan --diff origin/main...HEAD \
--sarif --output report.sarif --fail-on high
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: report.sarif
Two details do the heavy lifting:
security-events: write: the job needs this permission for the upload to succeed. Declare it on the job alongsidecontents: read:
jobs:
shipmoor:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
if: always(): the upload step must run even when the scan step failed the job. The runs you most want in the Security tab are exactly the ones where the gate fired, so you cannot let exit1short-circuit the upload.
The full workflow (and the composite-action form) is in GitHub Actions.
Exit codes: gate signal vs. error
The scan’s exit code and the SARIF are designed to be consumed together. A gate firing is not a tooling failure; the report is complete and worth uploading.
| Code | What happened | Is the SARIF valid? |
|---|---|---|
0 | Clean; no gate fired | Yes, complete (and empty of findings) |
1 | Findings met the --fail-on gate | Yes, complete. This is the gate working, not an error. |
2 | Usage or config error | No report produced |
3 | Scan failed | No report produced |
So the correct CI shape is: let exit 1 fail the check (that is its job) while still uploading the report, and reserve “tooling broke” alerts for 2 and 3, where no report exists.
Uploading even when the gate fails
If you want SARIF in code scanning on every run, including the ones where a blocking finding fired the gate, make sure the failing exit code does not abort the job before the upload:
- Simplest: keep
--fail-on highon the scan step and mark the upload stepif: always(), as above. The scan step still fails the job (so the check goes red), but the always-run upload step delivers the report first. - Decoupled: run the scan in a step that does not abort the job (capture its exit code), upload the SARIF, then fail the job yourself on the captured code:
- name: Run Shipmoor (don't abort on a blocking finding)
id: scan
run: |
"$HOME/.shipmoor/bin/shipmoor" scan --diff origin/main...HEAD \
--sarif --output report.sarif --fail-on high \
|| echo "code=$?" >> "$GITHUB_OUTPUT"
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: report.sarif
- name: Enforce the gate
if: steps.scan.outputs.code == '1'
run: exit 1
If you instead want code-scanning evidence with no blocking check at all, run with --fail-on none; you get full SARIF and a 0 exit on any successful scan.
Beyond GitHub
The SARIF is standard SARIF 2.1.0: GitLab, Azure DevOps, and most code-quality dashboards ingest it directly. For systems that prefer raw data, the JSON contract (shipmoor.scan.v1) carries everything the SARIF does and more (subtypes, change status, fingerprints), and, because both are built from one scan, omits the same suppressed findings.
Next
- GitHub Actions: the full workflow and the composite action.
- Output formats & exit codes: the complete output contract.