Exit codes and CI usage
The score-to-verdict mapping, the exit codes, and the GitHub Action.
Score and verdict
The score starts at 100. Each finding subtracts a fixed weight by severity: critical 25, high 10, medium 4, low 1, info 0. The floor is 0. One critical finding, or three high findings, gives a score below 80. Two high findings give exactly 80, which is still a pass.
| Score | Verdict | Exit code |
|---|---|---|
| 80 to 100 | pass | 0 |
| 50 to 79 | warn | 1 (or 2 with --strict) |
| 0 to 49 | fail | 2 |
Exit code 2 is also used when the score drops below
--fail-under (default 50), even if the verdict
would otherwise be pass or warn. --strict turns a
warn verdict into a fail before the exit code is picked, so warn
always exits 2 under --strict. A .paranoid.toml
at the repo root can set its own fail-under and
strict defaults, used whenever the matching flag is
not itself given; see Team policy file.
Exit code 3 means paranoid itself hit an error: a bad flag, not
a git repository, a git failure, an unreadable file. It is not
a finding. The report is not reliable when the exit code is 3. baseline, fix-prompt,
rules, version, and attest
keygen only ever exit 0 on success or 3 on error; they
have no score or verdict of their own to map to 1 or 2.
formal lock and formal unlock behave
the same way. formal status exits 2 when a locked
spec is drifted or missing, 0 otherwise, and 3 on error.
formal fill exits 0 when every hole was filled, 2
when any hole failed or was skipped, and 3 on error.
formal prove exits 0 when the draft matches the code
(or, with --continue, when every locked theorem is
proved and the model still matches), 2 when no matching model was
found or something is not proved, and 3 on error, including a
refused hotspot; see formal.
paranoid attest verify has its own, separate exit
code scheme, since it checks a signature, not a diff: 0 means
the signature is valid, 2 means it is invalid, 3 means
verification could not be attempted at all. See
Signing & attest.
The report lists which checks ran and which were skipped, with the reason. A pass can include skipped checks, so check the skipped-checks list in CI as well as the exit code.
GitHub Action
The composite action at alainrk/paranoid/action@main
runs verify on the pull request range, uploads the
report as an artifact, appends it to the job summary, optionally
posts a sticky PR comment, and sets the check status from the
verdict.
permissions:
contents: read
pull-requests: write # only needed for the sticky comment
jobs:
paranoid:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: alainrk/paranoid/action@main
with:
sandbox: none # or auto, when the runner has docker
claims: summary.md # optional
Action inputs
| Input | Default | Meaning |
|---|---|---|
| base | PR base ref (falls back to the PR's base SHA) | State before the agent worked |
| head | PR head | State after the agent worked |
| claims | none | Path to a free-text summary file |
| session | none | Path to an agent session file; ignored when claims is also set |
| sandbox | auto | auto, docker, podman, or none |
| offline | false | Skip registry lookups |
| fail-under | 50 | Fail when the score drops below this |
| strict | false | Treat a warn verdict as failure |
| baseline | none | Path to a baseline file, overriding the auto-detected .paranoid-baseline.json |
| no-baseline | false | Ignore any baseline file, including the auto-detected one |
| version | main | paranoid ref to install with go install |
| comment | true | Post or update a sticky PR comment; set false to skip it and drop the write permission |
| sarif | false | Also write paranoid-report.sarif |
Outputs: verdict (pass, warn, or fail) and score (0 to 100).
The action has no input for .paranoid.toml: it does
not need one, because the file is read from the checked-out
repository. A repo's [[disable]] and [sandbox]
keys apply exactly as they do outside CI. Its fail-under
key does not: the action always passes --fail-under
explicitly (this input's own default is 50), so cobra always sees
the flag as set and it always wins over the policy file.
strict is unaffected, since the action only adds
--strict when its own strict input is
true. See Team policy file.
Uploading findings to GitHub code scanning
Set sarif: "true" and add an
upload-sarif step after the action. This needs the
security-events: write permission, which the action
itself never requests.
permissions:
contents: read
pull-requests: write # only needed for the sticky comment
security-events: write # only needed to upload SARIF
jobs:
paranoid:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: alainrk/paranoid/action@main
with:
sandbox: none
sarif: "true"
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: paranoid-report.sarif
Findings appear with the repository's other code scanning alerts, with the rule's severity, message, and file location. Each rule links back to its entry in docs/rules.md. See Report & schema for the SARIF contract itself.
Adopting the Action on an existing repo
Generate and commit .paranoid-baseline.json once,
locally, before turning the Action on. The Action needs no extra
input for this: verify auto-detects the committed
file at the repo root the same way it does outside CI. Use the
baseline input only if you keep the file somewhere
else, and no-baseline: "true" for a workflow that
should ignore it entirely. See
Baseline & suppressions.
Other CI systems
The exit code can gate any CI system:
paranoid verify --base "origin/$BASE_REF" --format md --output report.md
This repository runs the same check on every push and pull
request; the report of each run is in the job summary.
--format sarif works the same way outside the
Action, for any CI system that accepts a SARIF file (GitHub's
own upload-sarif step, or a third-party code
scanning integration).