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@v4
- 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.
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). |
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: the action needs only
pull-requests: write(to post the comment) andcontents: read(for checkout). Grant exactly that in the workflow'spermissions:block — the examples above do. The token is used for nothing except listing/creating/updating the one PR comment. - 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 the comment step will fail for fork PRs unless you usepull_request_target— which checks out trusted base-branch workflow code; understand the security implications before switching, or setcomment: falsefor fork PRs.
This repository runs the action on its own PRs via
.github/workflows/pr-review.yml,
which uses: ./ (the local action.yml).
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