PR Explain is an AI-assisted Pull Request explanation service that turns a GitHub Pull Request into an evidence-backed explanation of what changed, why it changed, and what parts of the codebase may be affected.
The core idea is simple:
PR Explain is like SQL EXPLAIN for Pull Requests.
Instead of asking an AI model to blindly read a Pull Request and guess what happened, PR Explain first performs deterministic code analysis, builds a structured change graph, collects evidence, and determines potential impact. The AI model then turns those facts into a human-readable explanation.
If you've used EXPLAIN or EXPLAIN ANALYZE in SQL, the easiest way to understand PR Explain is to think of it as the same concept applied to code changes.
For example:
EXPLAIN
SELECT *
FROM users
WHERE email = 'alice@example.com';
The database doesn't ask an AI model to guess what the query does.
Instead, the database analyzes the query and produces a structured execution plan describing things such as:
- Which tables are accessed
- Which indexes may be used
- How operations are connected
- The expected execution strategy
- Potentially expensive operations
PR Explain applies the same philosophy to Pull Requests.
SQL Query
β
βΌ
Query Planner
β
βΌ
Execution Plan
β
βΌ
Human Understanding
Pull Request
β
βΌ
Code Change Analyzer
β
βΌ
Change Graph + Evidence + Impact
β
βΌ
AI Explanation
β
βΌ
Human Understanding
The important distinction is:
The AI is not the analyzer.
The deterministic analysis pipeline is responsible for discovering what actually changed.
The AI model is responsible for explaining those discovered facts.
A generic AI code-review system might look like:
Pull Request
β
βΌ
LLM
β
βΌ
"Here's what I think changed..."
PR Explain instead follows:
Pull Request
β
βΌ
Deterministic Analysis
β
βββ Changed files
βββ Changed symbols
βββ Imports
βββ Calls
βββ Relationships
βββ Evidence
βββ Impact
β
βΌ
Explanation Packet
β
βΌ
AI Model
β
βΌ
Human-readable explanation
This makes the AI model primarily a narrator, rather than the source of truth.
For example, if a Pull Request changes:
PaymentService.process_payment()
β
βββ PaymentRepository.create()
β
βββ EventPublisher.publish()
PR Explain first determines those relationships from the repository and Pull Request.
The AI then receives the resulting evidence and can explain:
This change modifies payment processing and affects both persistence and event publishing. The new behavior therefore has potential impact on the payment data path as well as downstream consumers of the payment event.
The model is explaining relationships that the analysis pipeline has already established rather than inventing them.
PR Explain analyzes Pull Requests and produces an explanation based on the actual repository contents and changes.
The analysis includes:
- Changed files
- Changed symbols
- Functions and classes
- Imports
- Call relationships
- Symbol relationships
- Relevant files outside the diff
- Line-level evidence
- Potential impact
- Claims about the change
- Revision-to-revision changes
The resulting explanation can be viewed in the web application and posted back to the Pull Request as a GitHub comment.
A Pull Request moves through the following pipeline:
GitHub Pull Request
β
βΌ
GitHub App Webhook
β
βΌ
FastAPI API
β
βΌ
Background Job
β
βΌ
Worker
β
βββ Fetch repository snapshot
βββ Analyze diff
βββ Parse symbols
βββ Build change graph
βββ Collect evidence
βββ Analyze impact
βββ Persist claims
β
βΌ
Explanation Packet
β
βΌ
AI Provider
β
βββββββββββββββββ
β β
βΌ βΌ
Ollama OpenAI
Local Production
β β
βββββββββ¬ββββββββ
βΌ
Explanation
β
βΌ
PostgreSQL
/ \
/ \
βΌ βΌ
React UI GitHub API
β
βΌ
PR Comment
PR Explain follows:
Deterministic analysis first. AI narration second.
The deterministic pipeline creates a structured representation of the change.
The AI receives a bounded explanation packet containing those facts.
This separation provides several benefits:
- Better grounding
- Lower risk of hallucinated files or functions
- Repeatable analysis
- Inspectable evidence
- Local/private AI inference
- Ability to change AI providers without changing the analysis pipeline
- AI failures do not destroy deterministic analysis
The GitHub App is the entry point into PR Explain.
It receives Pull Request webhook events and gives PR Explain permission to read repository contents and update Pull Request comments.
| Permission | Access | Purpose |
|---|---|---|
| Metadata | Read | Repository and installation metadata |
| Contents | Read | Read repository contents |
| Pull requests | Write | Create/update PR explanation comments |
PR Explain does not require GitHub Checks permissions and does not submit a Pull Request review score.
You need to create a GitHub App before PR Explain can receive Pull Request events.
GitHub's official documentation:
GitHub Apps Quickstart
Go to:
GitHub
β Settings
β Developer settings
β GitHub Apps
β New GitHub App
For an organization-owned application:
Organization
β Settings
β Developer settings
β GitHub Apps
β New GitHub App
Use:
GitHub App name: PR Explain
Configure the homepage URL to point to your deployed application or repository.
Enable:
Active: Yes
Set the webhook URL to:
https://<your-public-host>/api/webhooks/github
For local development, GitHub cannot directly reach localhost.
Use a tunnel such as Cloudflare Tunnel:
cloudflared tunnel --url http://localhost:8000
If the tunnel provides:
https://example.trycloudflare.com
configure:
https://example.trycloudflare.com/api/webhooks/github
as the GitHub App webhook URL.
Only the FastAPI API needs to be exposed.
Do not expose Ollama publicly.
Generate a strong random secret and configure the same value in:
GITHUB_WEBHOOK_SECRET=your-secret
The webhook secret is used to validate that incoming webhook requests originate from GitHub.
Never commit the webhook secret to Git.
Subscribe to:
installationinstallation_repositoriespull_request
For Pull Requests, PR Explain handles:
openedreopenedsynchronizeclosed
The closed event is received but does not trigger analysis.
From the GitHub App settings:
Private keys
β Generate a private key
GitHub will download a .pem file.
For example:
pr-explain.private-key.pem
Keep this file secure.
GitHub documentation:
Managing private keys for GitHub Apps
Configure:
GITHUB_APP_ID=123456
GITHUB_APP_PRIVATE_KEY_FILE=/absolute/path/to/pr-explain.private-key.pem
GITHUB_WEBHOOK_SECRET=your-webhook-secret
When using Docker Compose, the private key is mounted into the containers as:
/run/secrets/github-app.pem
The application reads the private key from the mounted file.
Keep this empty when using the file-based configuration:
GITHUB_APP_PRIVATE_KEY=
After creating the GitHub App:
- Open the GitHub App settings.
- Select Install App .
- Select the GitHub account or organization.
- Choose the repositories where PR Explain should operate.
- Complete the installation.
For security, use Only select repositories if PR Explain should only operate on specific repositories.
Install:
- Docker Desktop
- Git
- GitHub account
- GitHub App
- Cloudflare Tunnel or another webhook tunnel
Docker Compose runs the local application stack.
The local stack contains:
PostgreSQL
Ollama
Ollama model down
FastAPI API
Background worker
React frontend
git clone https://github.com/rajatrao/PR_explain.git
cd PR_explain
Create your environment file:
cp .env.example .env
At minimum, configure:
GITHUB_APP_ID=YOUR_APP_ID
GITHUB_APP_PRIVATE_KEY_FILE=/absolute/path/to/pr-explain.private-key.pem
GITHUB_WEBHOOK_SECRET=YOUR_WEBHOOK_SECRET
Local development uses Ollama running in Docker.
The default provider is:
LLM_PROVIDER=ollama
The default configuration is:
LLM_PROVIDER=ollama
OLLAMA_BASE_URL=http://127.0.0.1:11434
OLLAMA_MODEL=qwen3-coder:30b
OLLAMA_TIMEOUT_MS=180000
The first Docker Compose startup downloads the configured model into a persistent Ollama volume.
This allows the entire AI workflow to run locally without sending the explanation packet to a cloud AI provider.
Run:
docker compose up
The local services are:
| Service | Address |
|---|---|
| React frontend | http://localhost:5173 |
| FastAPI API | http://localhost:8000 |
| Ollama | http://localhost:11434 |
| PostgreSQL | localhost:5432 |
The first startup may take some time because the Ollama model needs to be downloaded.
Start a local webhook tunnel:
cloudflared tunnel --url http://localhost:8000
Then configure the GitHub App webhook:
https://<tunnel-host>/api/webhooks/github
For example:
https://example.trycloudflare.com/api/webhooks/github
Again:
Do not expose port 11434 publicly.
The webhook tunnel should only point to the FastAPI API.
Mermaid flowchart: GitHub, Cloudflare Tunnel, FastAPI API, ("PostgreSQL"), Background Worker, Ollama Docker, qwen3-coder:30b, React UI
The important architectural property is:
GitHub
β
βΌ
FastAPI
β
βΌ
Worker
β
βΌ
Ollama
The FastAPI API does not directly perform model inference.
The worker owns the long-running analysis and AI interaction.
This keeps webhook processing fast and prevents model inference from blocking HTTP requests.
Production deployments can use a public AI model such as OpenAI instead of running Ollama.
The provider is selected through configuration.
LLM_PROVIDER=ollama
Flow:
Worker
β
βΌ
Ollama
β
βΌ
Local AI Model
LLM_PROVIDER=openai
Flow:
Worker
β
βΌ
OpenAI API
β
βΌ
Cloud AI Model
No application code changes are required to switch between these configurations.
For production, configure:
LLM_PROVIDER=openai
LLM_BASE_URL=https://api.openai.com/v1
LLM_API_KEY=your-openai-api-key
LLM_MODEL=your-model-name
For example:
LLM_PROVIDER=openai
LLM_BASE_URL=https://api.openai.com/v1
LLM_API_KEY=...
LLM_MODEL=...
OpenAI API documentation:
OpenAI API Quickstart
Store the API key using your deployment platform's secret manager.
Do not commit:
LLM_API_KEY=...
to Git.
| Local Development | Production | |
|---|---|---|
| Provider | Ollama | OpenAI |
| Runtime | Docker | Cloud API |
| Model | qwen3-coder:30b |
Configured cloud model |
| Network | Local | Internet |
| API key | Not required | Required |
| Ollama | Required | Not required |
| Application code | Same | Same |
| Configuration | LLM_PROVIDER=ollama |
LLM_PROVIDER=openai |
The AI provider is an explicit configuration choice.
There is no automatic fallback between providers.
For example:
LLM_PROVIDER=openai
does not silently fall back to Ollama.
Likewise:
LLM_PROVIDER=ollama
does not automatically send data to a public AI provider.
This makes the data-flow and privacy decision explicit.
A typical production deployment looks like:
Mermaid flowchart: GitHub, GitHub App, HTTPS / Load Balancer, FastAPI API, ("PostgreSQL"), Background Worker, OpenAI API, React Web App, Pull Request Comment
The production deployment can therefore be thought of as:
GitHub
β
β Webhook
βΌ
βββββββββββββββββββ
β FastAPI API β
β β
β Webhooks / API β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β PostgreSQL β
β β
β Jobs / Analysis β
β Claims / Output β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Background β
β Worker β
β β
β Analysis + AI β
ββββββββββ¬βββββββββ
β
Explanation
Packet
β
βΌ
βββββββββββββββββββ
β OpenAI API β
β β
β AI Model β
βββββββββββββββββββ
One of the most important properties of PR Explain is making the AI data path explicit.
GitHub
β
βΌ
Self-hosted API
β
βΌ
Self-hosted Worker
β
βββ Repository snapshot
βββ Pull Request diff
βββ Symbols
βββ Evidence
βββ Explanation packet
β
βΌ
Local Ollama
In this mode, AI inference is performed locally on infrastructure you control.
GitHub
β
βΌ
Self-hosted API
β
βΌ
Self-hosted Worker
β
βββ Explanation packet
β
βΌ
OpenAI API
When:
LLM_PROVIDER=openai
the configured explanation packet is sent to the cloud AI provider.
This is an explicit configuration decision.
AI inference and repository analysis can take significantly longer than a normal HTTP request.
The webhook path therefore does not perform the complete analysis synchronously.
Instead:
GitHub
β
βΌ
Webhook
β
βΌ
FastAPI
β
βΌ
Create Job
β
βΌ
Return
The worker then performs:
Job
β
βββ Repository snapshot
βββ Diff analysis
βββ Symbol analysis
βββ Change graph
βββ Evidence
βββ Impact
βββ Explanation packet
βββ AI inference
βββ GitHub comment
This provides:
- Fast webhook responses
- Retryable background jobs
- Better failure isolation
- Long-running AI inference outside the HTTP request
- Separation between API and processing workloads
The current processing pipeline is:
snapshot_fetch
β
βΌ
diff_analysis
β
βΌ
symbol_analysis
β
βΌ
change_graph
β
βΌ
evidence
β
βΌ
impact
β
βΌ
claims_persisted
β
βΌ
explanation_packet_persisted
β
βΌ
explanation
β
βΌ
comment
The deterministic analysis stage produces facts about the Pull Request.
Examples include:
src/payment/service.py
src/payment/repository.py
tests/payment/test_service.py
PaymentService.process_payment
PaymentRepository.create
PaymentEventPublisher.publish
PaymentService.process_payment
β
βββ calls β PaymentRepository.create
β
βββ calls β PaymentEventPublisher.publish
The analysis can associate claims with the relevant source or diff evidence.
Files and symbols outside the direct diff can be identified when they are relevant to the change.
The AI model does not receive an unrestricted repository and is not expected to independently discover the entire change.
Instead, the worker constructs a bounded explanation packet.
Conceptually:
Explanation Packet
βββ Pull Request metadata
βββ Changed files
βββ Changed symbols
βββ Relationships
βββ Evidence
βββ Impact
βββ Claims
βββ Relevant context
The packet is intentionally bounded to prevent unnecessarily large model requests.
The AI model takes the explanation packet and turns it into human-readable language.
Conceptually:
Change Graph
+
Evidence
+
Impact
+
Claims
β
βΌ
Explanation Packet
β
βΌ
AI Model
β
βΌ
Human-readable Explanation
The model should explain the supplied facts rather than invent unsupported relationships.
PR Explain separates:
analysis_status
explanation_status
comment_status
This is important because deterministic analysis and AI narration are independent stages.
For example:
GitHub PR
β
βΌ
Analysis
β
βΌ
Claims Stored
β
βΌ
AI Explanation
β
βββ SUCCESS
β
βββ FAILURE
If the AI provider fails:
- Deterministic analysis is not discarded.
- Claims remain stored.
- Files and symbols remain available.
- Explanation can be retried.
- The Pull Request comment is not posted until explanation succeeds.
Similarly, if the GitHub comment update fails, the stored analysis and explanation remain available in the application.
PR Explain maintains one conversation-style explanation comment per Pull Request.
When a Pull Request receives a new commit:
New Commit
β
βΌ
New HEAD SHA
β
βΌ
New Analysis
β
βΌ
New Explanation
β
βΌ
Existing PR Comment Updated
This avoids creating a new comment for every commit.
The application can also compare claim sets across different Pull Request head SHAs to expose revision deltas.
The React frontend provides two primary views.
The Explain view provides the high-level change story and diagram.
It answers:
What changed and how does this change affect the system?
The Details view exposes the underlying evidence:
- Changed files
- Symbols
- Call relationships
- Line-level evidence
- Tests
- Relevant files outside the diff
- API facts
- Unknown facts
- Analysis status
- Explanation status
Both views are backed by the same stored analysis.
PR_explain/
βββ backend/
β βββ app/
β βββ api/
β βββ analysis/
β βββ llm/
β βββ worker/
β βββ ...
β
βββ frontend/
β βββ ...
β
βββ fixtures/
β βββ bench/
β
βββ .env.example
βββ docker-compose.yml
βββ README.md
βββ .gitignore
Main runtime components:
| Component | Responsibility |
|---|---|
backend |
API, GitHub integration, analysis and worker |
frontend |
React web application |
postgres |
Persistent application data |
worker |
Background analysis and AI explanation |
ollama |
Local AI inference |
ollama-pull |
Downloads the configured Ollama model |
fixtures |
Test and benchmark fixtures |
GITHUB_APP_ID=
GITHUB_APP_PRIVATE_KEY_FILE=
GITHUB_WEBHOOK_SECRET=
GITHUB_API_URL=https://api.github.com
LLM_PROVIDER=ollama
OLLAMA_BASE_URL=http://127.0.0.1:11434
OLLAMA_MODEL=qwen3-coder:30b
OLLAMA_TIMEOUT_MS=180000
LLM_PROVIDER=openai
LLM_BASE_URL=https://api.openai.com/v1
LLM_API_KEY=
LLM_MODEL=
DATABASE_URL=postgresql+psycopg://pr_explain:pr_explain@localhost:5432/pr_explain
POSTGRES_USER=pr_explain
POSTGRES_PASSWORD=pr_explain
POSTGRES_DB=pr_explain
APP_BASE_URL=http://localhost:5173
CORS_ORIGINS=http://localhost:5173
EXPLANATION_PACKET_CHAR_BUDGET=32000
FANOUT_CAP=50
MAX_CHANGED_SYMBOLS=80
Use .env.example as the source of truth for the current supported configuration.
The test suite can run without Ollama or GitHub.
cd backend
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytest
Tests cover areas including:
- OAuth fixture claims
- Citation validation
- Worker failure behavior
- Comment rendering
The test suite does not need to download an AI model or contact GitHub.
For lightweight local development:
cd backend
DATABASE_URL=sqlite:///./dev.db \
PYTHONPATH=. \
python -m app.seed
Start FastAPI:
DATABASE_URL=sqlite:///./dev.db \
PYTHONPATH=. \
uvicorn app.api:app --port 8000
In another terminal:
cd frontend
npm install
npm run dev
Then open the URL printed by the frontend development server.
PR Explain includes a model benchmark for testing different AI providers and models against frozen explanation packets.
Run:
python -m app.llm.bench
The benchmark produces a local JSON report covering areas such as:
- Grounding
- Structure
- Latency
- Process memory
The benchmark is intended to evaluate the model/provider configuration.
It is not a Pull Request quality score.
Do not commit:
.env
*.pem
GitHub App private keys
GitHub webhook secrets
LLM API keys
Database passwords
The GitHub App private key grants the application authentication capabilities as the GitHub App.
Keep it outside source control and inject it into the application using a secure file or secret-management mechanism.
For local development, Ollama is bound to:
127.0.0.1:11434
Do not expose Ollama through your GitHub webhook tunnel.
Only the FastAPI API needs to be publicly reachable:
Internet
β
βΌ
FastAPI :8000
not:
Internet
β
βΌ
Ollama :11434
Use your deployment platform's secret manager for:
GITHUB_APP_PRIVATE_KEY
GITHUB_WEBHOOK_SECRET
LLM_API_KEY
POSTGRES_PASSWORD
Do not store production secrets in the Git repository.
Before deploying PR Explain:
- Create the GitHub App
- Configure Metadata β Read
- Configure Contents β Read
- Configure Pull requests β Write
- Enable installation events
- Enable installation repository events
- Enable Pull Request events
- Generate GitHub App private key
- Store the private key securely
- Configure
GITHUB_APP_ID - Configure
GITHUB_WEBHOOK_SECRET - Install the GitHub App on the required repositories
- Deploy FastAPI with HTTPS
- Configure the GitHub webhook URL
- Deploy PostgreSQL
- Deploy the background worker
- Configure
LLM_PROVIDER=openai - Configure
LLM_BASE_URL - Configure
LLM_API_KEY - Configure
LLM_MODEL - Configure frontend production API origin
- Configure CORS
- Configure persistent database storage
- Configure application logging and monitoring
- Test a Pull Request from webhook through analysis to GitHub comment
- Verify that Ollama is not publicly exposed
- Verify that secrets are not present in logs
For production, use a real HTTPS server/application endpoint rather than a development webhook tunnel.
Suppose a developer opens:
PR #42
Add payment event publishing
The Pull Request modifies:
src/payment/service.py
src/payment/events.py
tests/payment/test_service.py
PR Explain receives:
GitHub Webhook
β
βΌ
FastAPI
β
βΌ
Background Worker
The worker analyzes the repository:
PaymentService.process_payment()
β
βββ PaymentRepository.create()
β
βββ PaymentEventPublisher.publish()
It collects:
Files
Symbols
Relationships
Evidence
Impact
Claims
It creates:
Explanation Packet
The configured AI provider then receives the packet.
Explanation Packet
β
βΌ
Ollama
β
βΌ
Local Model
Explanation Packet
β
βΌ
OpenAI
β
βΌ
Cloud Model
The generated explanation is stored and posted to GitHub:
Worker
β
βββ PostgreSQL
β
βββ GitHub API
β
βΌ
PR #42 Comment
Like SQL EXPLAIN, PR Explain first produces a structured representation of what is happening.
The AI then explains that representation.
The system determines what changed before generating a natural-language explanation.
The model is a narrator over deterministic analysis.
Developers can run the complete AI workflow locally using Docker and Ollama.
No cloud AI API is required for local inference.
Production can use OpenAI or another compatible provider through configuration without changing the core analysis pipeline.
Switching from:
LLM_PROVIDER=ollama
to:
LLM_PROVIDER=openai
is an explicit decision to move AI inference from local infrastructure to a cloud provider.
Analysis, explanation, and GitHub commenting are separate stages.
A model failure should not destroy deterministic analysis.
The GitHub App requests only the repository permissions required by PR Explain.
If you remember only one thing about PR Explain, remember this:
SQL
β
βΌ
βββββββββββββββββ
β EXPLAIN β
βββββββββ¬ββββββββ
β
βΌ
Execution Plan
β
βΌ
Human Understanding
Code
β
βΌ
βββββββββββββββββ
β PR EXPLAIN β
βββββββββ¬ββββββββ
β
βΌ
Change Graph + Evidence
β
βΌ
AI Model
β
βΌ
Human Understanding
PR Explain is EXPLAIN for Pull Requests.
It doesn't just ask an AI:
"What does this PR do?"
It first asks the codebase:
"What actually changed, what does it connect to, and what evidence supports that?"
Then it asks the AI:
"Now explain those facts to a human."
- Support for scalable async processing of PR explain request
- Improve explain notes to give more in-depth understanding of changes
- Add more support for overall review experience
MIT β for hackathon / research use.