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

> Discover how the code-review-graph GitHub Action delivers local, risk-scored PR reviews. It analyzes code on your CI runner, generating a knowledge graph for accurate risk assessments without external code sharing.

- Repository: [Tirth Kanani/code-review-graph](https://github.com/tirth8205/code-review-graph)
- Tags: how-to-guide
- Published: 2026-08-18

---

**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`](https://github.com/tirth8205/code-review-graph/blob/main/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`](https://github.com/tirth8205/code-review-graph/blob/main/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:

```yaml
- 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:

```bash
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:

```yaml
- 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)](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`:

```yaml
- 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:

```bash
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)](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/cli.py):

```bash
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)](https://github.com/tirth8205/code-review-graph/blob/main/scripts/render_pr_comment.py) transforms JSON into formatted markdown. Key implementation details:

```python
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:

```bash

# 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`](https://github.com/tirth8205/code-review-graph/blob/main/render_pr_comment.py):

```python
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:

```yaml
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`](https://github.com/tirth8205/code-review-graph/blob/main/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)](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)](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)](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)](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)](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`](https://github.com/tirth8205/code-review-graph/blob/main/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`](https://github.com/tirth8205/code-review-graph/blob/main/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`](https://github.com/tirth8205/code-review-graph/blob/main/render_pr_comment.py) and consumed by the shell script in [`action.yml`](https://github.com/tirth8205/code-review-graph/blob/main/action.yml) lines 106-124.

### What happens if the knowledge graph cache is corrupted?

The action's retry logic in [`action.yml`](https://github.com/tirth8205/code-review-graph/blob/main/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`](https://github.com/tirth8205/code-review-graph/blob/main/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.