Claude Code & Git Integration Guide

Claude Code & Git Integration Guide

Integration guide for connecting CodeGraph’s CPG analysis pipeline with Claude Code hooks and Git workflows.

Table of Contents

Overview

CodeGraph integrates with Claude Code through five hook scripts that inject CPG analysis data into the conversation at different lifecycle points. Combined with GoCPG git hooks that keep the CPG database synchronized, this creates a continuous feedback loop:

  1. Session starts – Claude receives project context and CPG statistics
  2. User asks a question – mentioned code entities are resolved against the CPG
  3. Claude edits a file – high-complexity methods in that file trigger warnings
  4. Claude commits code – the commit is analyzed for quality metrics, blast radius, and before/after impact
  5. Claude finishes a response – files mentioned in the response are checked for quality issues

All hooks communicate via JSON on stdin/stdout and return {"additionalContext": "..."} to inject markdown context into the conversation, or {} to remain silent.

Architecture

Hook system

Claude Code hooks are configured in .claude/settings.json. Each hook fires at a specific lifecycle event and receives event-specific JSON on stdin.

Claude Code Lifecycle
    |
    +-- SessionStart ---------> session_context.py -----> Project name, language, CPG stats
    |
    +-- UserPromptSubmit -----> enrich_prompt.py -------> CPG entity lookups for mentioned code
    |
    +-- PreToolUse (Edit) ----> pre_tool_use.py --------> Complexity warnings before file edit
    |
    +-- PostToolUse (Bash) ---> commit_analysis.py -----> Commit quality + blast radius + delta
    |
    +-- Stop -----------------> post_analysis.py -------> Quality check on files in response

Data flow

All hooks query the CPG database through one of two paths:

  1. GoCPG subprocess (gocpg.exe query --sql "...") – used by most hooks via _utils.run_gocpg_query(). No Python dependency on DuckDB required.

  2. DuckDB direct (import duckdb; con.execute(...)) – used by commit_analysis.py for the full analysis pipeline (CommitAnalyzer, CPGFreshnessChecker). Falls back to subprocess if DuckDB import fails.

Hook Script
    |
    +-- _utils.get_active_project()  --> config.yaml --> db_path
    |
    +-- _utils.run_gocpg_query(sql, db_path)  --> gocpg.exe query --> stdout
    |   OR
    +-- duckdb.connect(db_path)  --> direct SQL
    |
    +-- safe_json_output(markdown)  --> {"additionalContext": "..."} --> Claude Code

Shared utilities

All hooks import from .claude/hooks/_utils.py:

Function Purpose
get_active_project() Reads config.yaml, returns active project dict (name, db_path, language, domain)
run_gocpg_query(sql, db_path) Executes SQL via gocpg.exe query subprocess
run_cli_query(query) Executes natural language query via python -m src.cli query
extract_entities(text) Extracts CamelCase names, snake_case identifiers, and file paths from text
safe_json_output(context) Writes {"additionalContext": context} or {} to stdout
read_stdin_json() Parses JSON from stdin

Constants: PROJECT_ROOT (two levels up from hooks dir), GOCPG_BINARY (gocpg/gocpg.exe), CONFIG_PATH (config.yaml).

Hooks Reference

SessionStart: Project context

File: .claude/hooks/session_context.py Timeout: 10s Fires: Once when a Claude Code session begins

Reads the active project from config.yaml and runs gocpg stats to get CPG size. Outputs:

## Project Context
Active project: codegraph (python)
Domain: python_generic

### CPG Statistics
- Methods: 52341
- Calls: 111234
- Files: 1847
- Total nodes: 312000
- Total edges: 890000

This gives Claude immediate awareness of the project scope and available CPG data.

UserPromptSubmit: Entity enrichment

File: .claude/hooks/enrich_prompt.py Timeout: 15s Fires: On every user message

Extracts code entities from the user’s message (CamelCase class names, snake_case function names, file paths) and looks them up in the CPG. Returns location and complexity data for up to 3 entities.

## CodeGraph CPG Context

### CommitAnalyzer
  - `analyze_commit` at `src/dogfooding/commit_analyzer.py:298` (complexity: 12)
  - `_deduplicate_methods` at `src/dogfooding/commit_analyzer.py:115` (complexity: 3)

### cpg_freshness
  - `is_fresh` at `src/dogfooding/cpg_freshness.py:64` (complexity: 2)
  - `ensure_fresh` at `src/dogfooding/cpg_freshness.py:98` (complexity: 5)

This helps Claude locate code entities without manual file searching.

Interface Exposure Detection (M1)

For each resolved entity, the hook additionally checks which interface layers call it:

### CommitAnalyzer
  - `analyze_commit` at `src/dogfooding/commit_analyzer.py:298` (complexity: 12)
  **Exposed via:** CLI, MCP, REST API

The lookup_interface_exposure() function searches for calls to the entity from the 5 interface layers (CLI, REST API, TUI, MCP, ACP) via the nodes_call table in the CPG. This helps Claude assess the potential impact of changes on external interfaces.

PreToolUse: Complexity gate

File: .claude/hooks/pre_tool_use.py Timeout: 8s Fires: Before any Edit or Write tool call

Checks the target file for methods with CC > 15, high fan-out (> 30), TODO/FIXME markers, or deprecated code. Warns Claude before it modifies complex code:

## File Quality Warning
- `classify` (CC: 17, fan_out: 39) -- high complexity, review carefully
- `_score_domain` (CC: 16, has TODO/FIXME)

Non-source files (.md, .json, .yaml, etc.) are silently skipped.

Registration Completeness Check (CR2)

When editing files in scenarios/, services/, analysis/, security/ directories, the hook additionally checks whether all public functions in the file are registered in interface layers:

## Registration Warning
**Registration check:** `analyze_security`, `scan_endpoints` not called from any interface layer (CLI/API/TUI/MCP/ACP)

The check_registration_completeness() function extracts public functions (not starting with _ or test_) from the target file via CPG, then checks whether they are called from any interface layer. If some functions are registered and some are not, a warning is produced.

Files in the interface layers themselves (src/cli/, src/api/routers/, src/tui/commands/, src/mcp/tools/, src/acp/) are skipped.

PostToolUse: Commit analysis

File: .claude/hooks/commit_analysis.py Timeout: 60s Fires: After any Bash tool call containing git commit

The most complex hook. Runs a multi-phase analysis pipeline:

  1. CPG freshness check (2s) – compares cpg_git_state.commit_hash to git rev-parse HEAD
  2. Pre-update metrics capture (~1s) – snapshots current metrics for changed files (enables delta report)
  3. CPG update with force re-parse (40s) – runs gocpg update --force for accurate metrics
  4. Quality + blast radius analysis (16s) – queries nodes_method for CC, fan-out, TODO/FIXME, deprecated; queries call_containment for callers of changed methods
  5. Delta computation – compares pre-update vs post-update metrics

Output includes before/after impact, quality warnings, and blast radius:

## Commit Analysis Report
**Summary:** 3 files, 45 methods, 2 high-CC, 1 TODO/FIXME, 128 affected callers
**CPG status:** fresh

**Impact of changes:**
- `_get_fallback_domain`: CC 29->8 (-21), FanOut 18->5 (-13)

**High complexity methods:**
- `classify` (CC: 17)

**Blast radius:** 128 callers affected
- `classify` called by: `run_intent_classifier`, `IntentBenchmark._evaluate_single` +126 more

The hook only fires for git commit commands. Non-commit Bash commands (ls, git status, etc.) produce empty {}.

Fallback path: If DuckDB import fails, falls back to subprocess-based analysis (simpler, without blast radius or delta report).

Extended Analysis Phases (Code Review v3)

Beyond the base pipeline, the hook performs extended analysis:

Interface Impact Detection (CR1). If changed files are in interface layers (CLI, REST API, TUI, MCP, ACP), the hook reports which layers are affected and how many methods changed:

**Interface Impact:**
- CLI: 3 methods affected in `src/cli/audit_commands.py`
- MCP: 1 method affected in `src/mcp/tools/search.py`

Cross-Module Dependency Alerts (CR3). The hook analyzes which adjacent interface layers may need updates when one layer changes.

Go CPG Blast Radius (L1). If a Go CPG database is configured (go_db_path), the hook queries call_containment in both databases for a combined Python + Go blast radius.

Story Coverage Delta (L4). If interface impact is detected, the hook checks for story coverage gaps – for example, a function removed from CLI but still present in API.

Stop: Post-analysis

File: .claude/hooks/post_analysis.py Timeout: 10s Fires: When Claude finishes a response (excluding error stops)

Scans Claude’s response text for source file paths (src/..., pkg/..., etc.) and checks each file for quality issues via CPG:

  • TODO/FIXME markers
  • Debug code
  • Complexity spikes (methods with CC significantly above file average)
  • Fan-out hotspots (fan_out > 30)
  • Deprecated methods
## Post-Analysis
- `src/workflow/copilot.py`: complexity spikes: `classify` (CC:17), fan-out hotspots: `classify` (fan_out:39)
- `src/dogfooding/commit_analyzer.py`: 2 methods with TODO/FIXME markers

This surfaces quality issues in files that Claude discussed or modified during the conversation.

Post-Analysis Test & Registration Checks (L2)

The Stop hook additionally checks:

  • Missing tests: whether changed files contain public functions without corresponding tests
  • Unregistered interfaces: whether new functions are not called from any interface layer

Git Integration

GoCPG git hooks

GoCPG can install git post-commit hooks that trigger an incremental CPG update after every commit:

gocpg/gocpg.exe hooks install --repo=. --db=data/projects/codegraph.duckdb

This creates .git/hooks/post-commit which runs gocpg update in the background. The CPG stays synchronized with the latest code without manual intervention.

Check hook status:

gocpg/gocpg.exe hooks status --repo=.

Remove hooks:

gocpg/gocpg.exe hooks uninstall --repo=.

CPG freshness and automatic updates

The CPGFreshnessChecker (src/dogfooding/cpg_freshness.py) manages CPG synchronization:

  • is_fresh() – compares cpg_git_state.commit_hash (stored in DuckDB) against git rev-parse HEAD
  • commits_behind() – counts how many commits the CPG is behind HEAD
  • ensure_fresh(force=True) – runs gocpg update --force if stale

The --force flag is critical: incremental updates (gocpg update without --force) skip MethodMetricsPass, leaving cyclomatic_complexity, fan_in, and fan_out at 0 for new/modified methods. Force re-parse runs the full pipeline including metrics computation.

Interaction between git hooks and Claude Code hooks:

Developer commits (terminal)     Developer commits (Claude Code)
    |                                |
    v                                v
git post-commit hook             PostToolUse hook fires
    |                                |
    v                                +-- CPG freshness check
gocpg update (incremental)      |   +-- Pre-update metrics capture
    |                                +-- gocpg update --force (full re-parse)
    v                                +-- Quality + blast radius analysis
CPG updated (CC=0 for new)       +-- Delta report (before->after)
                                     |
                                     v
                                 Report injected into conversation

When both hooks are active, the git post-commit hook may run concurrently with the Claude Code PostToolUse hook. The Claude Code hook handles this gracefully – if the DuckDB file is locked by another gocpg.exe process, it catches the error and falls back to subprocess queries or produces partial output.

Configuration

Claude Code settings

The hook configuration lives in .claude/settings.json:

{
  "hooks": {
    "SessionStart": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "python .claude/hooks/session_context.py",
        "timeout": 10000
      }]
    }],
    "UserPromptSubmit": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "python .claude/hooks/enrich_prompt.py",
        "timeout": 15000
      }]
    }],
    "PreToolUse": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "python .claude/hooks/pre_tool_use.py",
        "timeout": 8000
      }]
    }],
    "PostToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "python .claude/hooks/commit_analysis.py",
        "timeout": 60000
      }]
    }],
    "Stop": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "python .claude/hooks/post_analysis.py",
        "timeout": 10000
      }]
    }]
  }
}

Matcher format: The matcher field is a regex string pattern, not an object. Use "" to match all events or "Bash" to match only the Bash tool. Object format ({"tools": ["Bash"]}) is no longer supported.

Timeout: In milliseconds. If a hook exceeds its timeout, Claude Code kills the process and continues without the hook’s output.

Project configuration

Hooks read the active project from config.yaml:

projects:
  active: codegraph
  registry:
    codegraph:
      db_path: data/projects/codegraph.duckdb
      source_path: .
      language: python
      domain: python_generic

The db_path is resolved relative to the project root. All hooks use _utils.get_active_project() to look up this configuration.

Setup

One-command setup

python -m src.cli.import_commands dogfood setup --repo . --db data/projects/codegraph.duckdb

This installs GoCPG git hooks and verifies the Claude Code hook configuration.

Manual setup

  1. Ensure GoCPG binary exists at gocpg/gocpg.exe (or set GOCPG_PATH environment variable)

  2. Ensure CPG database exists – import the project if needed: bash python -m src.cli import . --language python

  3. Install GoCPG git hooks: bash gocpg/gocpg.exe hooks install --repo=. --db=data/projects/codegraph.duckdb

  4. Copy .claude/settings.json with hook configuration (see Claude Code settings above)

  5. Verify with /doctor in Claude Code – should show no settings errors

Verify

# Check dogfooding pipeline status
python -m src.cli.import_commands dogfood status

# Check GoCPG hooks
gocpg/gocpg.exe hooks status --repo=.

# Test commit_analysis.py manually
echo '{"tool":"Bash","tool_input":{"command":"git commit -m \"test\""},"tool_result":"ok"}' | python .claude/hooks/commit_analysis.py

Troubleshooting

Hook produces empty {} for all events: - Check that config.yaml has an active project with valid db_path - Verify the DuckDB file exists at the configured path - Ensure gocpg/gocpg.exe exists and is executable

Entity enrichment finds nothing: - The CPG may not contain the queried entity. Run gocpg query --sql "SELECT COUNT(*) FROM nodes_method" to verify CPG is populated - Entity extraction only matches CamelCase names (> 3 chars), snake_case names (> 5 chars), and source file paths

Complexity gate never fires: - Only triggers for Edit and Write tools, not Bash - Only checks source files (.py, .go, .ts, etc.) - Threshold is CC > 15 – lower values are not reported

Commit analysis shows stale metrics (CC=0): - The hook uses --force flag by default. If metrics show 0, the force re-parse may have timed out (40s budget) - Check if another gocpg.exe process is running and holding a DuckDB lock - Try manual force update: gocpg/gocpg.exe update --force --input=. --output=<db>

Delta report not appearing: - Delta report only appears when CPG was stale before the update - If GoCPG git hooks or gocpg watch already updated the CPG, there are no pre-update metrics to compare

DuckDB lock error: - Another gocpg.exe process is holding the file. The hook handles this gracefully by falling back to subprocess queries - In production, this is rare since the hook fires after the Bash command completes

/doctor shows settings errors: - Ensure all matcher fields are strings ("" or "Bash"), not objects - Check JSON syntax in .claude/settings.json