Loading...
Loading...
Braintrust tracing for Claude Code - hook architecture, sub-agent correlation, debugging
npx skill4agent add parcadei/continuous-claude-v3 braintrust-tracing PARENT SESSION
+---------------------+
| SessionStart |
| (creates root) |
+----------+----------+
|
+----------v----------+
| UserPromptSubmit |
| (creates Turn) |
+----------+----------+
|
+--------------------+--------------------+
| | |
+---------v--------+ +--------v--------+ +--------v--------+
| PostToolUse | | PostToolUse | | PreToolUse |
| (Read span) | | (Edit span) | | (Task - inject) |
+------------------+ +-----------------+ +--------+--------+
|
+----------v----------+
| SUB-AGENT |
| SessionStart |
| (NEW root_span_id)|
+----------+----------+
|
+----------v----------+
| SubagentStop |
| (has session_id) |
+---------------------+| Hook | Trigger | Creates | Key Fields |
|---|---|---|---|
| SessionStart | Session begins | Root span | |
| UserPromptSubmit | User sends prompt | Turn span | |
| PreToolUse | Before tool runs | (modifies Task prompts) | |
| PostToolUse | After tool runs | Tool span | |
| Stop | Turn completes | LLM spans | |
| SubagentStop | Sub-agent finishes | (no span) | |
| SessionEnd | Session ends | (finalizes root) | |
Session (task span) - root_span_id = session_id
|
+-- Turn 1 (task span)
| |
| +-- claude-sonnet (llm span) - model call with tool_use
| +-- Read (tool span)
| +-- Edit (tool span)
| +-- claude-sonnet (llm span) - response after tools
|
+-- Turn 2 (task span)
| |
| +-- claude-sonnet (llm span)
| +-- Task (tool span) -----> [Sub-agent session - SEPARATE trace]
| +-- claude-sonnet (llm span)
|
+-- Turn 3 ...# PreToolUse hook injects:
[BRAINTRUST_TRACE_CONTEXT]
{"root_span_id": "abc", "parent_span_id": "xyz", "project_id": "123"}
[/BRAINTRUST_TRACE_CONTEXT]agentIdtotalTokenstotalToolUseCountcontenttool_input.prompttool_input.subagent_typesession_idroot_span_idroot_span_idagentIdsession_idroot_span_idSubagentStop fires -> writes session_id to temp file
PostToolUse (Task) -> reads temp file -> adds child_session_id to Task span metadataTask.agentIdTask.child_session_idroot_span_id~/.claude/state/braintrust_sessions/
{session_id}.json # Per-session state{
"root_span_id": "abc-123",
"project_id": "proj-456",
"turn_count": 5,
"tool_count": 23,
"current_turn_span_id": "turn-789",
"current_turn_start": 1703456789,
"started": "2025-12-24T10:00:00.000Z",
"is_subagent": false
}~/.claude/state/braintrust_global.json # Cached project_id
~/.claude/state/braintrust_hook.log # Debug log# View hook logs in real-time
tail -f ~/.claude/state/braintrust_hook.log
# Check if session has state
cat ~/.claude/state/braintrust_sessions/*.json | jq -s '.'
# Verify environment
echo "TRACE_TO_BRAINTRUST=$TRACE_TO_BRAINTRUST"
echo "BRAINTRUST_API_KEY=${BRAINTRUST_API_KEY:+set}"# List recent sessions
uv run python -m runtime.harness scripts/braintrust_analyze.py --sessions 5
# Analyze last session
uv run python -m runtime.harness scripts/braintrust_analyze.py --last-session
# Replay specific session
uv run python -m runtime.harness scripts/braintrust_analyze.py --replay <session-id>
# Find sub-agent traces (orphaned roots)
uv run python -m runtime.harness scripts/braintrust_analyze.py --agent-stats# Enable verbose logging
export BRAINTRUST_CC_DEBUG=true
# Test hooks manually
echo '{"session_id":"test-123","type":"resume"}' | \
bash "$CLAUDE_PROJECT_DIR/.claude/plugins/braintrust-tracing/hooks/session_start.sh"
# Test PreToolUse (Task injection)
echo '{"session_id":"test-123","tool_name":"Task","tool_input":{"prompt":"test"}}' | \
bash "$CLAUDE_PROJECT_DIR/.claude/plugins/braintrust-tracing/hooks/pre_tool_use.sh"TRACE_TO_BRAINTRUST=true.claude/settings.local.jsonecho $BRAINTRUST_API_KEYtail -20 ~/.claude/state/braintrust_hook.log--agent-statsagentIdcurrent_turn_span_idrm ~/.claude/state/braintrust_sessions/*.jsonrm ~/.claude/state/braintrust_global.json| File | Purpose |
|---|---|
| Shared utilities, API, state management |
| Creates root span, handles sub-agent context |
| Creates Turn spans per user message |
| Injects trace context into Task prompts |
| Creates tool spans, captures agent/skill metadata |
| Creates LLM spans, finalizes Turns |
| Finalizes session, triggers learning extraction |
| Query and analyze traced sessions |
| Per-session state files |
| Debug log |
| Variable | Required | Default | Description |
|---|---|---|---|
| Yes | - | Set to |
| Yes | - | API key for Braintrust |
| No | | Project name |
| No | | Verbose logging |
| No | | API endpoint |
metadata.agent_typesubagent_typemetadata.skill_nametool_inputtool_outputagentIdroot_span_id = session_idsession_idsession_idchild_session_idagentIdsession_idsession_idtypecwd{
"session_id": "...",
"type": "start|resume|compact|clear",
"cwd": "...",
"env": {...}
}task_span_idagentIdtotalTokenstotalToolUseCountcontenttool_input.prompttool_input.subagent_typesession_idroot_span_idroot_span_idsubagent_typesession_idinterface SessionStartInput {
session_id: string;
type: "start" | "resume" | "compact" | "clear";
cwd: string;
env: { [key: string]: string };
// NO: prompt, tool_context, task_span_id, parent_span_id
}session_id# 1. Export parent session
braintrust_analyze.py --replay <parent-session-id> > parent_traces.json
# 2. Query for orphaned sub-agent traces (those created during parent's time window)
braintrust_analyze.py --agent-stats > all_agent_traces.json
# 3. Correlate in Python:
# - Parent Task spans -> agentId, timestamps, subagent_type
# - Orphaned traces -> root_span_id, timestamps
# - Match by timing and type