Overview¶
CodeGraph provides automated code review through two mechanisms:
- Claude Code hooks — standalone Python scripts in
.claude/hooks/that run automatically at lifecycle points (SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop). They communicate via JSON stdin/stdout. - CLI review pipeline —
python -m src.cli reviewcommand with its own aggregator, scope-aware filtering, and SARIF output.
Both share the review_pipeline configuration and parse scope awareness, but implement filtering differently.
Hooks¶
| Hook | Lifecycle | Timeout | Purpose |
|---|---|---|---|
session_context.py |
SessionStart | 10s | Detect project, check CPG freshness, load parse scope |
enrich_prompt.py |
UserPromptSubmit | 15s | Inject CPG context into prompts (max 3 entities) |
pre_tool_use.py |
PreToolUse | 8s | Complexity gate (CC > 15), fan-out check, registration completeness |
commit_analysis.py |
PostToolUse (Bash) | 60s | Quality analysis after git commit with CPG freshness update |
post_analysis.py |
Stop | 10s | Dead code, missing tests, complexity spikes, interface gaps |
cli_error_monitor.py |
PostToolUse (Bash) | 5s | Detect 6 CLI error patterns |
cli_error_monitor Patterns¶
The monitor detects these specific error patterns in Bash output:
- Database not found
DatabaseNotConfiguredError- DuckDB
__spec__import bug ImportError/ModuleNotFoundError- Python traceback (generic)
RuntimeWarning(module import)
Timeout Budgets¶
commit_analysis.py has an internal 58s budget (2s margin from 60s timeout):
| Phase | Budget | Description |
|---|---|---|
| CPG freshness check | 2s | Compare HEAD commit with DB |
| CPG update (if stale) | 40s | Re-parse via gocpg |
| Quality analysis | 16s | CC, TODO/FIXME, blast radius |
Thresholds¶
| Hook | Threshold | Value |
|---|---|---|
pre_tool_use.py |
Cyclomatic Complexity | 15 |
pre_tool_use.py |
Fan-Out | 30 |
commit_analysis.py |
Cyclomatic Complexity | 10 |
commit_analysis.py |
Fan-Out | 30 |
commit_analysis.py |
Max files | 20 |
post_analysis.py |
CC spike ratio | 2.0x |
post_analysis.py |
Max files | 5 |
enrich_prompt.py |
Max entities | 3 |
Architecture¶
stdin (JSON) → Hook Script → stdout (JSON)
↓
_utils.py (shared helpers: gocpg queries, scope loading)
_feedback.py (ReviewFeedback/ReviewFinding dataclasses)
_metrics.py (JSONL performance logging)
_session_cache.py (file-based project context cache, TTL=3600s)
_project_detector.py (auto-detect project by CWD/source_path matching)
Support Modules¶
| Module | Key exports |
|---|---|
_utils.py |
read_stdin_json(), safe_json_output(), run_gocpg_query(), extract_entities(), load_parse_scope(), is_partial_scope(), tests_in_scope() |
_feedback.py |
ReviewFinding (8 fields), ReviewFeedback (9 fields), FINDING_TYPES, SEVERITY_ORDER |
_metrics.py |
timed_hook() context manager, log_hook_metric() |
_session_cache.py |
get_project_with_cache(), set_cached_project(), invalidate_cache() — TTL 3600s, stored in .claude/.cache/session_project.json |
_project_detector.py |
detect_project(cwd) — matches CWD against projects.registry[*].source_path, supports ${VAR:-default} env vars |
Structured Feedback Protocol¶
All hooks output JSON with optional additionalContext (markdown rendered to user):
{
"additionalContext": "## Review Report\n..."
}
The ReviewFeedback class in _feedback.py generates structured markdown with:
- Severity-sorted findings table
- Scope disclaimer (when parse scope is partial)
- Suppressed count (findings filtered due to scope)
- Duration and metadata
Finding Types¶
Six finding types are defined in FINDING_TYPES:
| Type | Description | Used by |
|---|---|---|
complexity |
High cyclomatic complexity or fan-out | pre_tool_use, commit_analysis, post_analysis |
dead_code |
Unreachable or uncalled code | commit_analysis |
missing_test |
No test file for source module | post_analysis |
blast_radius |
Many callers affected by change | commit_analysis |
security |
Security vulnerability | CLI review pipeline |
interface_gap |
Public functions not exposed via any interface | pre_tool_use, post_analysis |
Parse Scope Awareness¶
When a CPG is built with excluded directories (e.g., only backend without tests/frontend), scope-aware filtering is applied. The behavior differs between hooks and the CLI review pipeline:
Hooks Behavior¶
| Finding Type | Condition | Hook | Action |
|---|---|---|---|
dead_code |
Scope is partial | commit_analysis.py |
Add disclaimer block to report |
blast_radius |
Scope is partial | commit_analysis.py |
Add disclaimer block to report |
missing_test |
include_tests=false |
post_analysis.py |
Suppress entirely, increment suppressed_count |
complexity |
— | all | Not affected (per-method metric) |
security |
— | all | Not affected (real vulnerability) |
interface_gap |
— | all | Not affected |
CLI Review Pipeline Behavior¶
The CLI review command uses ReviewAggregator (src/review/aggregator.py) with different logic:
| Finding Type | Condition | Action |
|---|---|---|
dead_code |
Scope is partial + demote_dead_code_partial_scope=true |
Demote to info, set scope_limited=True |
blast_radius |
Scope is partial + demote_dead_code_partial_scope=true |
Demote to info, set scope_limited=True |
missing_test |
suppress_tests_outside_scope=true + tests not in scope |
Suppress (return None) |
A disclaimer block is added to the report when scope is limited.
CLI Review Command¶
# Review changes against a base ref
python -m src.cli review --base-ref HEAD~3
# Review with specific database
python -m src.cli review --db data/projects/myproject.duckdb --base-ref HEAD~3
# Review staged changes
python -m src.cli review --staged
# Review specific files
python -m src.cli review --files src/api/main.py src/auth.py
# Output formats
python -m src.cli review --format json --output-file report.json
python -m src.cli review --format sarif --output-file report.sarif
python -m src.cli review --base-ref HEAD~5 --sarif-file out.sarif
# Skip security analysis
python -m src.cli review --no-security
| Argument | Description |
|---|---|
--db PATH |
Path to DuckDB CPG database (default: active project) |
--base-ref REF |
Git base ref for diff review (e.g. HEAD~3, origin/main) |
--staged |
Review staged changes only |
--files FILE... |
Review specific files |
--no-security |
Skip security analysis |
--format {markdown,json,sarif} |
Output format (default: markdown) |
--output-file PATH |
Write output to file |
--sarif-file PATH |
Write SARIF output separately |
Exit codes: 0 = clean or medium/low only, 1 = critical or high findings.
Metrics & Monitoring¶
Hooks log execution metrics to data/hook_metrics.jsonl:
{"timestamp": "2026-03-06T12:00:00Z", "hook": "commit_analysis", "duration_ms": 1234.5, "findings": 3, "project": "codegraph", "status": "ok"}
View statistics:
python -m src.cli dogfood hooks-status
python -m src.cli dogfood hooks-status --last 50
python -m src.cli dogfood hooks-status --hook commit_analysis
Graceful Degradation¶
All hooks follow fail-open design: - Missing gocpg binary: skip CPG queries, output empty context - Corrupt/missing DB: skip analysis, warn in metrics - Timeout: partial results returned within budget - Import errors: fallback to subprocess path (commit_analysis uses DuckDB direct → gocpg subprocess) - Any unhandled exception: caught at top level, empty JSON output
Configuration¶
# config.yaml
review_pipeline:
# Project detection
auto_detect_project: true # Auto-detect project by CWD
detect_by_source_path: true # Match CWD against projects.registry[*].source_path
# CPG freshness
auto_reparse: prompt # Behavior on stale CPG: prompt | auto | off
freshness_check_on_session: true # Check CPG freshness at SessionStart
max_reparse_timeout_seconds: 60 # Timeout for CPG re-parse
stale_threshold_commits: 0 # Commits before CPG is stale (0 = any new commit)
# Output
max_findings_in_summary: 10 # Max findings shown in summary
# Parse scope awareness
scope_aware_filtering: true # Enable scope-aware filtering
suppress_tests_outside_scope: true # Suppress missing_test when tests not in scope
demote_dead_code_partial_scope: true # Demote dead_code/blast_radius in CLI review
show_scope_disclaimer: true # Show disclaimer when scope is partial
# Metrics
metrics_enabled: true # Enable JSONL metrics logging
metrics_file: data/hook_metrics.jsonl # Metrics output file
fail_open: true # Continue on errors (fail-open)
# Dogfooding (used by commit_analysis.py)
dogfooding:
enabled: true
auto_update_cpg: true
cpg_update_timeout: 40 # Timeout for CPG update in commit_analysis
analysis_timeout: 16 # Timeout for quality analysis phase
Adding a New Hook¶
- Create a Python script in
.claude/hooks/ - Read JSON from stdin with
read_stdin_json()from_utils.py - Wrap logic in
with timed_hook("hook_name")from_metrics.py - Output JSON to stdout with
safe_json_output()from_utils.py - Register in
.claude/settings.json
settings.json Format¶
{
"hooks": {
"PreToolUse": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/my_hook.py",
"timeout": 8000
}
]
}
]
}
}
| Field | Description |
|---|---|
| Top-level key | Lifecycle event: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop |
matcher |
Tool name filter (e.g. "Bash"). Empty string = all tools |
type |
Always "command" |
command |
Full path to Python script |
timeout |
Timeout in milliseconds |
Directory: .claude/hooks/
Last updated: March 2026