paranoid_
Core

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.

ScoreVerdictExit code
80 to 100pass0
50 to 79warn1 (or 2 with --strict)
0 to 49fail2

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

InputDefaultMeaning
basePR base ref (falls back to the PR's base SHA)State before the agent worked
headPR headState after the agent worked
claimsnonePath to a free-text summary file
sessionnonePath to an agent session file; ignored when claims is also set
sandboxautoauto, docker, podman, or none
offlinefalseSkip registry lookups
fail-under50Fail when the score drops below this
strictfalseTreat a warn verdict as failure
baselinenonePath to a baseline file, overriding the auto-detected .paranoid-baseline.json
no-baselinefalseIgnore any baseline file, including the auto-detected one
versionmainparanoid ref to install with go install
commenttruePost or update a sticky PR comment; set false to skip it and drop the write permission
sariffalseAlso 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).