Claude Code Integration
Claude Code is Anthropic’s official CLI for Claude. It provides a hook system that allows external commands to run on various events. dp integrates with Claude Code by installing hooks that capture tool call failures (and optionally all tool calls) for analysis.
Quick Setup
dp init --source claude-code
This command updates ~/.claude/settings.json to add a PostToolUseFailure hook. It’s idempotent—safe to run multiple times.
What Are Claude Code Hooks?
Claude Code fires hooks at specific lifecycle events. The most relevant for dp:
- PostToolUseFailure: Fires when a tool call fails (tool not found, invalid input, execution error)
- PostToolUse: Fires after every tool call, whether it succeeds or fails
Hooks receive a JSON payload describing the event. They execute asynchronously—Claude Code doesn’t wait for the hook to complete, so dp processing never slows down your session.
Hook Configuration
Default Setup (Failures Only)
dp init --source claude-code writes this to ~/.claude/settings.json:
{
"hooks": {
"PostToolUseFailure": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "dp record --source claude-code",
"timeout": 5000
}
]
}
]
}
}
The matcher: ".*" means “match all sessions.” Every time a tool call fails, Claude Code pipes the failure JSON to dp record --source claude-code via stdin. dp parses the JSON, extracts fields, and writes a desire record to ~/.dp/desires.db.
Full Tracking (Successes + Failures)
To track all tool invocations (not just failures), enable full tracking:
dp init --source claude-code --track-all
This adds two additional hooks:
{
"hooks": {
"PostToolUseFailure": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "dp record --source claude-code",
"timeout": 5000
},
{
"type": "command",
"command": "dp ingest --source claude-code",
"timeout": 5000
}
]
}
],
"PostToolUse": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "dp ingest --source claude-code",
"timeout": 5000
}
]
}
]
}
}
Now:
PostToolUseFailureruns bothdp record(for desires) anddp ingest(for invocations)PostToolUserunsdp ingestfor all successful calls
This generates more data—every tool call fires a hook—so only enable it if you need invocation-level analytics (success rates, call frequency, session analysis, etc.).
Hook Payload Format
Claude Code passes a JSON object on stdin. Example for a failed tool call:
{
"session_id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"hook_event_name": "PostToolUseFailure",
"tool_name": "read_file",
"tool_use_id": "toolu_01ABC123",
"tool_input": {
"file_path": "/tmp/nonexistent.txt"
},
"error": "File not found: /tmp/nonexistent.txt",
"cwd": "/home/user/project",
"transcript_path": "/home/user/.claude/transcripts/2026-02-09-session.json",
"permission_mode": "normal"
}
Field Mapping
dp’s claude-code plugin maps these fields to universal Fields:
| Claude Code Field | Universal Field | Notes |
|---|---|---|
tool_name | ToolName | Required |
session_id | InstanceID | Session identifier |
tool_input | ToolInput | Preserved as raw JSON |
cwd | CWD | Working directory |
error | Error | Error message (only present on failures) |
Everything else goes into Extra:
tool_use_id: Claude’s internal ID for the tool calltranscript_path: Path to the session transcript filehook_event_name: Which hook fired (PostToolUseFailureorPostToolUse)permission_mode: Permission level (normal,strict, etc.)
These fields are stored in the metadata column as JSON, available for queries but not indexed.
Commands Used by Hooks
dp record --source claude-code
Records a desire (failed tool call). Reads JSON from stdin, extracts fields using the claude-code plugin, generates a UUID and timestamp, and writes to the desires table.
Example manual invocation:
echo '{"tool_name":"read_file","error":"unknown tool","session_id":"test","cwd":"/tmp"}' \
| dp record --source claude-code
dp ingest --source claude-code
Records an invocation (any tool call, success or failure). Reads JSON from stdin, extracts fields, sets is_error based on presence of error field, and writes to the invocations table.
Example manual invocation:
echo '{"tool_name":"Read","session_id":"test","cwd":"/tmp"}' \
| dp ingest --source claude-code
Idempotency
dp init --source claude-code is idempotent. If hooks already exist, it won’t add duplicates. It merges the new hooks into the existing hooks config, preserving any other hooks you’ve configured.
Run it multiple times safely:
dp init --source claude-code
dp init --source claude-code
dp init --source claude-code
The second and third runs do nothing (hook already present).
Switching from default to full tracking:
dp init --source claude-code # adds PostToolUseFailure → dp record
dp init --source claude-code --track-all # adds PostToolUse/PostToolUseFailure → dp ingest
The second command adds the ingest hooks without removing the record hook. This is safe—both commands write to different tables (desires vs invocations).
Hook Execution Details
- Timeout: 5 seconds (configurable in the JSON). If dp takes longer, Claude Code kills the process.
- Stdin/Stdout: Hook receives JSON on stdin. Stdout/stderr are discarded (not shown to the user).
- Exit Code: Ignored. Hook failures don’t affect Claude Code.
- Async: Hook runs in the background. Claude Code continues immediately.
Typical execution time: ~5-10ms for dp record, ~10-20ms for dp ingest.
Troubleshooting
Hooks Not Firing
Check that dp is in your PATH:
which dp
If it’s not found, Claude Code can’t execute the hook. Install dp to a location in PATH (like $HOME/go/bin or /usr/local/bin).
Verify the hooks are installed:
cat ~/.claude/settings.json | jq '.hooks'
You should see PostToolUseFailure with a dp record command.
No Desires Being Recorded
Manually trigger a failure and check the database:
echo '{"tool_name":"test_tool","error":"test error","session_id":"manual","cwd":"/tmp"}' \
| dp record --source claude-code
dp list --limit 1
If the desire appears, hooks are working. If not, check that ~/.dp/desires.db is writable.
Enable verbose logging (if dp supported it—currently it doesn’t) or use strace to debug:
strace -e trace=open,write dp record --source claude-code < payload.json
Database Locked Errors
SQLite uses WAL mode for concurrent reads/writes, but if another process holds a write lock (like a long-running transaction), writes may block briefly. This is rare—most writes complete in milliseconds.
If you see “database is locked” errors frequently:
- Check for long-running
dpcommands (likedp exporton a huge database) - Verify no other process is holding the database open
- Check disk I/O (slow disks can cause lock contention)
SQLite’s busy timeout is set to 5 seconds—writes retry automatically during that window.
Data Storage
Desires Table
Schema:
CREATE TABLE desires (
id TEXT PRIMARY KEY,
tool_name TEXT NOT NULL,
tool_input TEXT,
error TEXT NOT NULL,
source TEXT,
session_id TEXT,
cwd TEXT,
timestamp TEXT NOT NULL,
metadata TEXT
);
Each dp record writes one row.
Invocations Table
Schema:
CREATE TABLE invocations (
id TEXT PRIMARY KEY,
source TEXT NOT NULL,
instance_id TEXT,
host_id TEXT,
tool_name TEXT NOT NULL,
is_error INTEGER NOT NULL,
error TEXT,
cwd TEXT,
timestamp TEXT NOT NULL,
metadata TEXT
);
Each dp ingest writes one row. is_error is 1 if error field was present in the payload, 0 otherwise.
Query Examples
List all Claude Code desires:
dp list --source claude-code
View aggregated paths:
dp paths
Inspect a specific tool name:
dp inspect read_file
Show invocation stats (requires --track-all):
dp stats --invocations
List all invocations from a session:
sqlite3 ~/.dp/desires.db "SELECT tool_name, is_error, timestamp FROM invocations WHERE instance_id = 'session-id-here' ORDER BY timestamp;"
Performance Notes
- Desire recording: ~5ms per record (dominated by SQLite write + fsync)
- Invocation ingestion: ~10ms per record (slightly larger payloads)
- Database size: ~1KB per desire, ~1.5KB per invocation (including JSON metadata)
- After 10,000 desires: ~10MB database
- After 100,000 invocations: ~150MB database
WAL mode keeps reads fast even during writes. Queries are instant up to ~1M records.
PreToolUse Hook: Active Correction
Beyond recording failures, dp can actively intercept and correct tool calls using the PreToolUse hook.
Setup
dp pave --hook
This adds a PreToolUse hook to ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "dp pave-check",
"timeout": 3000
}
]
}
]
}
}
How It Works
The PreToolUse hook fires before every tool call. dp pave-check reads the JSON payload from stdin and performs two checks:
1. Tool Name Blocking
If the tool name matches a tool-name alias (e.g., read_file → Read), the hook exits with code 2 and writes an error message to stderr. Claude Code blocks the call and shows the message, prompting the AI to use the correct tool name.
2. Parameter Rewriting
If the tool name is valid but parameters contain known mistakes, the hook rewrites them via updatedInput. For example, if you have a rule --cmd scp --flag r R:
Input payload:
{
"tool_name": "Bash",
"tool_input": {"command": "scp -r file.txt host:/"}
}
Hook output (exit 0):
{
"hookSpecificOutput": {
"permissionDecision": "allow",
"updatedInput": {"command": "scp -R file.txt host:/"},
"additionalContext": "Corrected: -r → -R"
}
}
Claude Code uses the corrected command value transparently. The AI sees the additionalContext note explaining what changed.
Exit Code Protocol
| Exit Code | Meaning | Behavior |
|---|---|---|
| 0 (no output) | Allow as-is | Tool call proceeds unchanged |
| 0 (with JSON) | Allow with corrections | Tool call proceeds with updatedInput |
| 2 | Block | Tool call is rejected, error shown to AI |
Fail-Safe Design
The hook is designed to never break your workflow:
- JSON parse errors → allow (don’t block on malformed payloads)
- Database unavailable → allow (don’t block if store is down)
- Rule application errors → allow (skip broken rules)
- Timeout (3s) → Claude Code kills the hook and proceeds
Creating Correction Rules
See dp alias for the full flag reference. Quick examples:
# Flag correction
dp alias --cmd scp --flag r R
# Command substitution
dp alias --cmd grep --replace rg
# Regex replacement
dp alias --tool Bash --param command --regex "curl -k" "curl --cacert cert.pem"
See dp pave for more details on the hook mechanism and troubleshooting.
Next Steps
- Configuration: Customize database path, known tools, etc.
- Command Reference: Explore all dp commands
- Writing a Plugin: Build a plugin for another AI tool
- Architecture: Understand dp’s internals