Braintrust Tracing for Claude Code
Comprehensive guide to tracing Claude Code sessions in Braintrust, including sub-agent correlation.
Architecture Overview
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 Event Flow
| Hook | Trigger | Creates | Key Fields |
|---|---|---|---|
| SessionStart | Session begins | Root span | session_id, root_span_id |
| UserPromptSubmit | User sends prompt | Turn span | prompt, turn_number |
| PreToolUse | Before tool runs | (modifies Task prompts) | tool_input.prompt |
| PostToolUse | After tool runs | Tool span | tool_name, input, output |
| Stop | Turn completes | LLM spans | model, tokens, tool_calls |
| SubagentStop | Sub-agent finishes | (no span) | session_id of sub-agent |
| SessionEnd | Session ends | (finalizes root) | turn_count, tool_count |
Trace Hierarchy
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 ...
Sub-Agent Tracing: What Works and What Doesn't
What Doesn't Work
SessionStart doesn't receive the Task prompt.
We tried injecting trace context into Task prompts via PreToolUse:
# PreToolUse hook injects:
[BRAINTRUST_TRACE_CONTEXT]
{"root_span_id": "abc", "parent_span_id": "xyz", "project_id": "123"}
[/BRAINTRUST_TRACE_CONTEXT]
But SessionStart only receives session metadata, not the modified prompt. The injected context is lost.
What DOES Work
Task spans in parent session contain everything:
agentId- identifier for the sub-agent runtotalTokens,totalToolUseCount- metricscontent- full agent response/summarytool_input.prompt- original task prompttool_input.subagent_type- agent type (e.g., "oracle")
SubagentStop hook receives the sub-agent's session_id:
- This equals the sub-agent's orphaned trace
root_span_id - Allows correlation between parent Task span and child trace
The Correlation Pattern
Current state: Sub-agents create orphaned traces (new root_span_id).
Correlation method:
- Query parent session's Task spans for agent metadata
- Match
agentIdor timing with orphaned traces - Sub-agent's
session_id= its trace'sroot_span_id
Future solution (not yet implemented):
SubagentStop fires -> writes session_id to temp file
PostToolUse (Task) -> reads temp file -> adds child_session_id to Task span metadata
This would link: Task.agentId + Task.child_session_id -> orphaned trace root_span_id
State Management
Per-Session State Files
~/.claude/state/braintrust_sessions/
{session_id}.json # Per-session state
Each session file contains:
{
"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
}
Global State
~/.claude/state/braintrust_global.json # Cached project_id
~/.claude/state/braintrust_hook.log # Debug log
Debugging Commands
Check if Tracing is Active
# 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}"
Query Braintrust Directly
# 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
Debug Hook Execution
# 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"
Troubleshooting Checklist
-
No traces appearing:
- Check
TRACE_TO_BRAINTRUST=truein.claude/settings.local.json - Verify API key:
echo $BRAINTRUST_API_KEY - Check logs:
tail -20 ~/.claude/state/braintrust_hook.log
- Check
-
Sub-agents not linking:
- This is expected - sub-agents create orphaned traces
- Use
--agent-statsto find agent activity - Correlate via timing or
agentIdin parent Task span
-
Missing spans:
- Check
current_turn_span_idin session state - Ensure Stop hook runs (turn finalization)
- Look for "Failed to create" errors in log
- Check
-
State corruption:
- Remove session state:
rm ~/.claude/state/braintrust_sessions/*.json - Clear global cache:
rm ~/.claude/state/braintrust_global.json
- Remove session state:
Key Files
| File | Purpose |
|---|---|
.claude/plugins/braintrust-tracing/hooks/common.sh | Shared utilities, API, state management |
.claude/plugins/braintrust-tracing/hooks/session_start.sh | Creates root span, handles sub-agent context |
.claude/plugins/braintrust-tracing/hooks/user_prompt_submit.sh | Creates Turn spans per user message |
.claude/plugins/braintrust-tracing/hooks/pre_tool_use.sh | Injects trace context into Task prompts |
.claude/plugins/braintrust-tracing/hooks/post_tool_use.sh | Creates tool spans, captures agent/skill metadata |
.claude/plugins/braintrust-tracing/hooks/stop_hook.sh | Creates LLM spans, finalizes Turns |
.claude/plugins/braintrust-tracing/hooks/session_end.sh | Finalizes session, triggers learning extraction |
scripts/braintrust_analyze.py | Query and analyze traced sessions |
~/.claude/state/braintrust_sessions/ | Per-session state files |
~/.claude/state/braintrust_hook.log | Debug log |