Proven
Decisions

ADR-0020: Proven's command line and GitHub Action: the exact flags, inputs, outputs and exit codes, taken from Semgrep and the Opengrep action

Status: accepted · Date: 2026-10-21 Decided by: the owner, 2026-10-21: "Yes for the ADR, and reviewdog." · Written by: the advisor (claude-code, claude-opus-5-5), 2026-10-21 Amended: 2026-10-25, decided by the owner: point 3 gains the github-token input; point 4's SARIF upload runs in its own job (point 5 is job-scoped on GitHub), so each swap changes more lines than first named Amended: 2026-10-26, on the owner's order of 2026-10-25 (one comment per pull request): point 7 now has the Action's own poster write the one summary comment, and reviewdog is no longer run; point 3's github-token row follows Amends: ADR-0017, point 4: the lab's comment is posted by the Action's own poster (point 7 below) Extends: ADR-0016 (a drop-in replacement for pipeline scanners) Follows: ADR-0013 (no hardcoded settings), ADR-0017 (the Gitea lab) Trigger: the owner, on what is decided before the build window and what is built in it (planning/CONVERSATION_2026-10-21_advisor.md, hop 12)

Context

ADR-0016 decided that Proven drops into a pipeline in place of Semgrep or Opengrep. It did not list the exact names. The owner (translated from Polish): "The same goes for checking which call parameters Proven should accept, the drop-in replacement in GitHub Actions. All of it, the data sets and the call, goes in as decisions." The code is written from 22 October; the interface is decided now.

How the two tools are run in GitHub Actions today, read on 21 October:

  • Semgrep documents one job: the container semgrep/semgrep, actions/checkout, then run: semgrep ci, with SEMGREP_APP_TOKEN from the repository's secrets. On a pull_request trigger it scans only what the pull request changed, with no baseline variable set. Its free variant runs semgrep scan --config auto. (sample CI configs, CLI reference)
  • Opengrep has a community action, platform-sec/opengrep-action, with inputs target, patterns, config, output-format, severity, include, exclude, timeout, jobs, strict, baseline-commit, and outputs results-file, json-file, sarif-file, findings-count. (opengrep-action)
  • SARIF into GitHub goes through github/codeql-action/upload-sarif, input sarif_file, optional category, with the job permission security-events: write. (upload-sarif)

Decision

1. The command line

proven scan [TARGET] reviews a directory or the changes since a baseline, locally. proven ci reviews a pull request in a pipeline: it reads the pipeline's context (GITHUB_EVENT_PATH, GITHUB_BASE_REF, GITHUB_SHA; Gitea Actions sets the same names) and reviews only the change against the merge base.

flagenvironment variabledefaultmeaning (same as Semgrep's unless said)
--text · --json · --sarifPROVEN_OUTPUT_FORMATtextformat on standard output
--text-output=<file> · --json-output=<file> · --sarif-output=<file>—nonealso write that format to a file; any number at once
-o, --output=<file>PROVEN_OUTPUTstandard outputwrite the chosen format to a file
--error · --no-errorPROVEN_ERRORscan: off · ci: onexit 1 when there are findings
--baseline-commit=<ref>PROVEN_BASELINE_COMMIT, then SEMGREP_BASELINE_COMMIT, then SEMGREP_BASELINE_REFci: the pull request's basereview only what changed since that commit
--config=<file>PROVEN_CONFIGproven.yaml if present, else the defaultsProven's configuration file (ADR-0013); a Semgrep registry value such as auto or p/… is refused with a message saying what --config takes
--include=<glob> · --exclude=<glob>PROVEN_INCLUDE · PROVEN_EXCLUDEall files the change toucheswhich changed files are reviewed
--severity=<level>PROVEN_SEVERITYINFOlowest severity reported: INFO, WARNING, ERROR (SARIF note, warning, error)
--timeout=<seconds>PROVEN_TIMEOUTfrom the configurationthe whole review's time limit; a review that runs out returns what it proved so far
--sampleSAMPLE_MODE=trueoffsample adapters: no network, no keys (ADR-0001)
--quiet · --verbose—neitheras Semgrep
--version · --help——as usual

Unknown flags are refused with the list of known ones. Secrets are environment variables only and never flags: METERLESS_RUNTIME_TOKEN (ADR-0019), ZETARIS_API_KEY and ZETARIS_ORG_ID (ADR-0015), and a token for posting on pull requests where that is switched on.

2. Exit codes

codemeaningas Semgrep
0review done; no findings, or findings without --erroryes
1review done; findings, with --error (the default in ci)yes
2fatal error: Proven could not reviewyes
7the configuration was refused when it loaded (ADR-0013)Semgrep's 7 is "missing configuration"

An outside service that fails is not exit 2: the review degrades, says which service failed, and exits 0 or 1 (ADR-0001).

3. The GitHub Action

An action.yml at the root of this repository, a container action on Proven's published image, used as uses: saphan-ai/proven@<full commit SHA>. Its inputs use the Opengrep action's names where the meaning is the same:

inputdefaultmaps to
target.the scan target
confignone--config
output-formatjson/sarifjson/sarif, json, sarif or text, as in the Opengrep action
severityINFO--severity
include · excludenone--include · --exclude
timeoutnone--timeout
stricttrue--error
baseline-committhe pull request's base--baseline-commit
sample-modefalse--sample
commentfalsepost the review on the pull request; needs pull-requests: write
github-tokenthe workflow's token (github.token)the token the Action's own poster writes the one summary comment with when comment is on; the scan itself never receives a token (amended 2026-10-25 and 2026-10-26)

Outputs, with the Opengrep action's names: results-file, json-file, sarif-file, findings-count; and one of Proven's own, unresolved-count, the findings whose evidence did not settle them.

The Opengrep action's patterns, jobs and opengrep-version have no meaning in Proven and are not inputs. GitHub only warns about an input an action does not define, so Proven cannot refuse one; the documentation names them.

4. Before and after, in the documentation

Semgrep → Proven: the container semgrep/semgrep becomes Proven's image; run: semgrep ci becomes run: proven ci; SEMGREP_APP_TOKEN becomes the Proven secrets above. A upload-sarif step, if there is one, moves into its own job that downloads the SARIF the scan job saved as an artifact, because GitHub grants permissions per job and point 5 gives security-events: write only to the upload (amended 2026-10-25; the swap is then 27 changed lines, not 9).

Opengrep action → Proven: uses: platform-sec/opengrep-action@<sha> becomes uses: saphan-ai/proven@<sha>; target, config, output-format, severity, include, exclude, timeout, strict and baseline-commit stay as they are; patterns is removed. The outputs a later step reads keep their names. The SARIF upload moves into its own job as in the Semgrep swap (amended 2026-10-25; the swap is then 24 changed lines, not 4).

SARIF into GitHub code scanning: github/codeql-action/upload-sarif with sarif_file set to Proven's SARIF and category: proven, so it does not replace another tool's analysis of the same commit.

5. Permissions

contents: read always. security-events: write only for the SARIF upload, which on GitHub means its own job, since permissions are granted per job. pull-requests: write only when comment is on. Proven asks for nothing more.

6. Runners

The owner pointed to GitHub's documentation on GitHub-hosted and self-hosted runners. Three facts from it shape the Action:

  • A container action "can only execute on runners with a Linux operating system", and "self-hosted runners must use a Linux operating system and have Docker installed" (about custom actions). So uses: saphan-ai/proven@… runs on Linux runners. On macOS and Windows runners, the documentation shows the command line in a run: step instead, from a release binary.
  • A GitHub-hosted runner is a fresh virtual machine in GitHub's cloud. It cannot reach a team's private network, so a Zetaris or a Nightshift that is not reachable from the internet is out of its reach, and any model key it uses has to be stored as a GitHub secret.
  • GitHub "recommend[s] that you only use self-hosted runners with private repositories", because "forks of your public repository can potentially run dangerous code on your self-hosted runner machine".

The owner (translated from Polish): "But self-hosted too. Not everyone will want to hand over their model API keys. Locally too, or even above all."

So the self-hosted runner is Proven's primary way to run live, and the GitHub-hosted runner the secondary one:

self-hosted runner (primary)GitHub-hosted runner
models and keysstay on the team's own network: Nightshift and local models, no key in GitHubthe team's keys as GitHub secrets, or sample mode
Zetaris, Nightshiftreached on the private networkonly what is reachable from the internet
the same workflowyes: only runs-on: differsyes

The protections a self-hosted runner needs, because Proven runs the code under review (its Prover runs tests on the change). The documentation sets them out, and Proven's own public repository uses them:

  1. One job per runner, then gone: the runner is registered as ephemeral, in its own virtual machine, so nothing a job leaves behind reaches the next.
  2. Pull requests from forks wait for a maintainer before any job runs, with GitHub's setting that requires approval to run workflows from outside contributors.
  3. The runner holds no secret beyond what the review needs: Proven's own tokens, scoped to Proven, never a deploy key or a cloud credential.
  4. The runner's network reaches only what the review needs: the Zetaris and Nightshift endpoints, the package registries; nothing else on the private network.
  5. A team that cannot meet these keeps live mode for private repositories, as GitHub recommends.

7. One comment on the pull request, written by the Action's own poster

Amended 2026-10-26, in place (this point first read: "Comments on the pull request, through reviewdog"; the owner's order of 2026-10-25 is: one comment per pull request).

The pull request gets one comment, the summary: Proven's Markdown (OUTPUT_FORMATS.md), written by the Action's own poster, action/post-comment.sh, and edited in place on every run. The same script serves GitHub and Gitea (GET /user, the comments list and PATCH of one comment). The Action no longer runs reviewdog: line comments beside the summary would be more than one comment, so none are written. Per-line results travel in the SARIF file, which a team sends to code scanning (point 4), a channel that is not a comment.

  • Proven writes SARIF and Markdown; the Action's comment input posts the Markdown only. A team that runs reviewdog on its own can still give it Proven's SARIF; the Action does not.
  • This still replaces the Gitea comment adapter of ADR-0017, point 4, as the owner accepted, with one path on GitHub and Gitea. It costs code of our own (one shell file, tested offline), which the reviewdog path had avoided; the owner chose one comment over that saving.
  • The comments list is read to its end before anything is created, so a comment on a late page is edited, never duplicated; a walk that cannot reach the end creates nothing and fails the step.

8. Other pull-request review tools

AI reviewers (for example Qodo's PR-Agent, CodeRabbit, GitHub Copilot's code review, Anthropic's Claude Code action) are usually installed as GitHub Apps or actions that post comments, each with its own commands and settings. Proven does not copy their commands. It meets them where their output meets the pipeline: comments on the pull request (point 7), SARIF in code scanning, and a failing check. Matching one of them input for input is not part of this decision; their documentation was not read for it.

Consequences

  • Moving from either tool changes the image or the uses: line and the secrets, and keeps every other line. The lab (ADR-0017) runs the same workflow on Gitea, which checks that claim.
  • Every flag and input is a setting with a documented default (ADR-0013); each is tested at its default and at one other value.
  • Semgrep's rule registry, its app token and Opengrep's rule patterns are not copied: Proven is not a pattern scanner.
  • The exact JSON fields and the SARIF properties that carry each finding's evidence are written with the code, from 22 October, within ADR-0016's rules.

On this page