code-review-graph ships a composite GitHub Action (action.yml at the repo
root) that posts a risk-scored, graph-aware review comment on every pull
request — think of it as a hosted AI review bot (Greptile-style), except the
analysis is local-first: the knowledge graph is built and queried entirely
on your CI runner, and no source code is sent to any external service.
On each PR run the action:
- Installs
code-review-graphfrom PyPI. - Restores the cached
.code-review-graph/SQLite graph (or builds it from scratch on a cache miss) and incrementally re-parses the files changed by the PR. - Runs
code-review-graph detect-changes --base origin/<base-branch>to get risk-scored functions, affected execution flows, and test gaps. - Renders a markdown report (via
scripts/render_pr_comment.py) and upserts a single sticky PR comment — the same comment is updated on every push, so the PR thread is never spammed. - Optionally fails the job when the overall risk score crosses a threshold
(
fail-on-risk).
# .github/workflows/code-review-graph.yml
name: code-review-graph
on:
pull_request:
permissions:
contents: read
pull-requests: write
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: tirth8205/code-review-graph@v2.3.6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}That is the whole setup. The default GITHUB_TOKEN provided by Actions is
sufficient — no PAT, no API key, no third-party service.
Self-hosted runners must be version 2.327.1 or newer. The composite action
uses Node 24-based GitHub actions, including actions/setup-python@v6,
actions/cache@v6, and the recommended actions/checkout@v7 example.
To turn the review into a merge gate:
- uses: tirth8205/code-review-graph@v2.3.6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
fail-on-risk: high| Input | Required | Default | Description |
|---|---|---|---|
github-token |
yes | — | Token used to post the sticky PR comment via the GitHub API. The workflow's default GITHUB_TOKEN works when the job has pull-requests: write. |
comment |
no | true |
Post (and keep updated) the sticky PR comment. Set to false to run analysis/gating without commenting. |
fail-on-risk |
no | none |
Fail the job when the overall risk score reaches a level: none (never fail), high (risk ≥ 0.70), critical (risk ≥ 0.85). |
python-version |
no | 3.12 |
Python version used to run code-review-graph (3.10+ supported). |
| Output | Description |
|---|---|
comment-file |
Runner-local path to the rendered markdown report. Use with comment: false when a separate trusted workflow will publish it. |
detect-changes produces a 0.0–1.0 overall risk score (max across changed
functions; see code_review_graph/changes.py:compute_risk_score for the
scoring factors: flow participation, community crossing, test coverage,
security-sensitive names, caller count). The action maps it to levels:
| Level | Score |
|---|---|
| low | < 0.40 |
| medium | 0.40 – 0.69 |
| high | 0.70 – 0.84 |
| critical | ≥ 0.85 |
- Overall risk score and level, with counts of changed functions, affected flows, and test gaps.
- Risk-scored changes — a table of the top changed symbols ordered by risk, with file:line locations and test-coverage status.
- Affected execution flows — which entry-point flows the change touches, ordered by criticality.
- Test gaps — changed functions with no direct test coverage.
- Token savings — how many tokens the graph-backed report saved versus
reading every changed file in full. This is the same
context_savingsestimate the CLI's Token Savings panel shows (achars / 4approximation labelledestimated: true— see REPRODUCING.md for the calibration methodology). - A
Powered by code-review-graphfooter.
The comment starts with a hidden HTML marker
(<!-- code-review-graph-report -->). The action looks the marker up via
gh api on each run and PATCHes the existing comment instead of creating a
new one (a "sticky" comment).
The action caches the .code-review-graph/ directory (the SQLite graph
database) with actions/cache:
- Key:
code-review-graph-schema9-<runner.os>-<hashFiles(lockfiles)>, where the lockfile hash covers common Python/JS/Go/Rust/Ruby/PHP lockfiles (uv.lock,poetry.lock,requirements*.txt,package-lock.json,go.sum,Cargo.lock, …). - Schema segment:
schema9tracks the database schema version (LATEST_VERSIONincode_review_graph/migrations.py). It is bumped when the schema changes so stale caches are never restored across incompatible versions. - Restore keys: fall back to any cache for the same OS and schema, so a lockfile change still reuses the previous graph.
- On cache hit: the action runs
code-review-graph update --base origin/<base-branch>, which re-parses only the files that differ from the PR's base ref. If the restored database turns out to be unusable, it falls back to a fullbuild. - On cache miss: a full
code-review-graph buildruns (one-time cost; subsequent PR runs are incremental).
- Token scope: direct commenting needs
contents: readfor checkout andpull-requests: writeto post the comment. In the split fork-safe setup, the analysis workflow needs onlycontents: read; the trusted commenter needs onlyactions: readandpull-requests: write. Grant exactly those permissions in each workflow. - Local-first: analysis runs entirely on the runner. No code, diff, or metadata leaves GitHub's infrastructure; there is no external API, account, or key.
- Untrusted input: all dynamic values (
github.base_ref, the PR number, action inputs) are passed to scripts through environment variables, never interpolated into shell commands. The markdown renderer escapes table/markup characters and strips control characters from symbol names and file paths before they reach the comment body, on top of the server-side_sanitize_name()sanitization. - Pinning: when consuming the action from another repository, pin
uses:to a release tag or commit SHA rather than@main. - Fork PRs:
pull_requestruns from forks receive a read-onlyGITHUB_TOKEN, so they cannot post the comment directly. Use an unprivilegedpull_requestworkflow withcomment: false, upload thecomment-fileas an artifact, and publish it from a separate trustedworkflow_runworkflow. See.github/workflows/pr-review.ymland.github/workflows/pr-review-comment.yml. GitHub loads theworkflow_runworkflow from the default branch, so the trusted commenting half becomes active only after that workflow is merged. The privileged workflow must verify the source event and analyzed commit, extract only underrunner.temp, cap and validate the artifact, and add its own sticky marker before posting. Avoidpull_request_targetwith a checkout of PR code because it can execute untrusted code with a privileged token (details).
This repository runs the action on its own PRs via
.github/workflows/pr-review.yml,
which runs the local action.yml without write permissions and uploads the
rendered report. The trusted
pr-review-comment.yml workflow
validates that artifact and posts the sticky comment without checking out or
executing PR-controlled code.
The markdown rendering and risk gating logic lives in
scripts/render_pr_comment.py (stdlib
only, unit-tested in tests/test_action_render.py) rather than inline YAML,
so it can be tested and reused:
code-review-graph detect-changes --base origin/main | \
python scripts/render_pr_comment.py # markdown to stdout
python scripts/render_pr_comment.py --input report.json \
--fail-on-risk high --quiet # gate only: exit 3 on breach