How the code-review-graph GitHub Action Performs Local Risk-Scored PR Reviews

The code-review-graph GitHub Action runs a fully local analysis on your CI runner, building a code knowledge graph to compute risk scores for changed symbols and posting a single sticky PR comment—without ever sending source code to external services.

The code-review-graph open-source tool provides a local-first alternative to cloud-based code review services. By analyzing Python codebases through a persistent SQLite knowledge graph, it generates fine-grained risk assessments entirely within the GitHub Actions environment. This article explains the complete technical workflow as implemented in tirth8205/code-review-graph.

Architecture Overview: The 9-Step Local Analysis Pipeline

The composite action defined in action.yml orchestrates a deterministic pipeline where every step executes on the runner. Here's how the system transforms raw source changes into an actionable risk report.

1. Python Environment Setup

The action begins by pinning the runtime environment. In [action.yml lines 45-49](https://github.com/tirth8205/code-review-graph/blob/main/action.yml#L45-L49), it invokes actions/setup-python@v7 with a default Python version of 3.12:

- name: Setup Python
  uses: actions/setup-python@v7
  with:
    python-version: ${{ inputs.python-version }}

This ensures reproducible parsing behavior across all workflow executions.

2. Package Installation

The tool installs from PyPI rather than building from source, guaranteeing a stable release version. Lines 50-53 execute:

python -m pip install --quiet code-review-graph

This pulls the CLI tools including code-review-graph build, update, and detect-changes.

3. Knowledge Graph Caching

Performance depends on caching the SQLite database between runs. The cache key incorporates the schema version to force invalidation on breaking format changes:

- name: Cache graph database
  uses: actions/cache@v4
  with:
    key: crg-graph-${{ runner.os }}-schema9-${{ hashFiles('**/schema.json') }}
    restore-keys: crg-graph-${{ runner.os }}-schema9-

The schema version schema9 originates from [code_review_graph/migrations.py](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/migrations.py) where LATEST_VERSION is defined.

4. Diff Base Resolution

The action determines what to compare against. For pull requests, it fetches the base branch; for other events, it falls back to HEAD~1. The result exports as CRG_BASE:

- name: Determine base ref
  run: |
    if [[ "${{ github.event_name }}" == "pull_request" ]]; then
      echo "CRG_BASE=${{ github.event.pull_request.base.sha }}" >> $GITHUB_ENV
    else
      echo "CRG_BASE=$(git rev-parse HEAD~1)" >> $GITHUB_ENV
    fi

This environment variable drives all subsequent incremental analysis.

5. Incremental Graph Building

The action attempts an efficient update before falling back to full rebuild:

code-review-graph update --base "$CRG_BASE" || code-review-graph build

update parses only files modified since the base commit. build performs a complete codebase ingestion when the cache is corrupted or schema-incompatible.

6. Risk-Scored Detection

The core analysis executes via [code_review_graph/cli.py](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/cli.py):

code-review-graph detect-changes --base "$CRG_BASE" > report.json

This produces a JSON payload containing:

  • Per-symbol risk scores (0.0–1.0)
  • Execution-flow impact analysis
  • Test-coverage gaps for changed functions

The detect-changes command is the analytical engine that transforms graph topology into actionable metrics.

7. Markdown Comment Rendering

The Python script [scripts/render_pr_comment.py](https://github.com/tirth8205/code-review-graph/blob/main/scripts/render_pr_comment.py) transforms JSON into formatted markdown. Key implementation details:

MARKER = "<!-- code-review-graph-report -->"
RISK_THRESHOLDS = {
    "critical": 0.85,
    "high": 0.70,
    "medium": 0.50,
    "low": 0.00,
}

def risk_level(score: float) -> str:
    for level, threshold in RISK_THRESHOLDS.items():
        if score >= threshold:
            return level
    return "low"

The hidden HTML marker enables the action to locate and update its own comment across multiple workflow runs.

8. Sticky Comment Upsert

Using GitHub CLI, the action implements idempotent comment management:


# Search for existing comment

COMMENT_ID=$(gh api repos/{owner}/{repo}/issues/{number}/comments \
  --jq '.[] | select(.body | contains("<!-- code-review-graph-report -->")) | .id')

# Update existing or create new

if [[ -n "$COMMENT_ID" ]]; then
  gh api --method PATCH repos/{owner}/{repo}/issues/comments/$COMMENT_ID -f body="$BODY"
else
  gh api --method POST repos/{owner}/{repo}/issues/{number}/comments -f body="$BODY"
fi

This "sticky" behavior keeps PR discussions clean with a single evolving comment.

9. Risk Gate Enforcement

Optional workflow failure thresholds are implemented in render_pr_comment.py:

if args.fail_on_risk != "none":
    overall_score = max(float(e.get("risk_score", 0)) for e in data["priorities"])
    if risk_level(overall_score) in {"high", "critical"}:
        sys.exit(3)  # Non-zero exit fails the GitHub Action

Exit code 3 signals the GitHub Actions framework to mark the check as failed, blocking merge when configured.

Complete Workflow Example

Deploy the action with this minimal configuration:

name: Local Risk-Scored PR Review
on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  analyze:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Required for base ref comparison

      
      - uses: tirth8205/code-review-graph@v1
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          python-version: "3.12"
          fail-on-risk: high  # Fail on high or critical risk

Risk Score Interpretation

The markdown comment presents a formatted table generated by _functions_table() in render_pr_comment.py:

Risk Score Level Interpretation
0.85–1.00 Critical High complexity, deep call chains, no test coverage
0.70–0.84 High Significant execution flow changes, partial coverage
0.50–0.69 Medium Moderate impact, existing test gaps
0.00–0.49 Low Simple changes, well-covered or isolated

Each row includes the symbol's qualified name, file location, and test coverage status.

Source Code References

Path Responsibility
[action.yml](https://github.com/tirth8205/code-review-graph/blob/main/action.yml) Composite action definition, orchestrates all 9 steps
[scripts/render_pr_comment.py](https://github.com/tirth8205/code-review-graph/blob/main/scripts/render_pr_comment.py) JSON-to-markdown conversion, risk gate logic, marker management
[code_review_graph/cli.py](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/cli.py) detect-changes command implementation
[code_review_graph/migrations.py](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/migrations.py) Schema versioning for cache invalidation
[code_review_graph/skills.py](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/skills.py) Pre-commit hook integrations, --brief flag support

Summary

  • Local-first execution: All analysis runs on the GitHub Actions runner; source code never leaves your infrastructure
  • Incremental performance: SQLite caching with schema-versioned invalidation avoids full rebuilds
  • Sticky PR comments: Single auto-updating comment with hidden HTML marker prevents notification spam
  • Configurable risk gates: fail-on-risk input supports none, high, or critical thresholds
  • Transparent scoring: Risk thresholds (critical ≥ 0.85, high ≥ 0.70) are hardcoded in render_pr_comment.py lines 38-40

Frequently Asked Questions

How does the action find and update its previous comment?

The scripts/render_pr_comment.py script embeds a hidden HTML marker <!-- code-review-graph-report --> in every comment body. Subsequent workflow runs query the GitHub API for comments containing this string, then PATCH the existing comment if found or POST a new one otherwise. This marker-based lookup is defined in render_pr_comment.py and consumed by the shell script in action.yml lines 106-124.

What happens if the knowledge graph cache is corrupted?

The action's retry logic in action.yml lines 79-88 attempts code-review-graph update first. If this incremental update fails—whether from cache corruption, schema mismatch, or missing base commit—it automatically falls back to code-review-graph build for a complete fresh parse. The schema version in the cache key (currently schema9) prevents most corruption scenarios by invalidating incompatible caches.

Can I run this on private repositories without code exposure?

Yes. According to the code-review-graph source code, the only network requests are: (1) PyPI package installation, (2) optional GitHub API calls to post the comment, and (3) actions/cache operations to persist the SQLite database. The actual source code analysis, graph construction, and risk computation execute entirely within the runner's local filesystem.

Why does the action exit with code 3 instead of 1 for risk failures?

Exit code 3 is intentionally chosen to distinguish risk gate failures from other error conditions in render_pr_comment.py. This convention allows workflow authors to differentiate between infrastructure problems (exit 1) and intentional policy blocks (exit 3) when configuring continue-on-error or notification rules in their .github/workflows/ definitions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →