Arthur + Claude Code
Instrument your Claude Code sessions with end-to-end tracing
Overview
When you use Claude Code as an AI pair-programmer, the session is a black box. You see the final result, but not the intermediate reasoning: which tools were called, which failed, how many LLM round-trips it took, or how long each step ran. Without that visibility, debugging a bad session or comparing two approaches is guesswork.
The Arthur Claude Code integration closes that gap. It attaches to Claude Code's event lifecycle and emits structured OpenInference spans to Arthur Engine β one trace per user prompt, containing every tool call and LLM response that Claude made to answer it. Sessions are linked by a shared arthur.session attribute, so you can filter across an entire coding session in Arthur's trace explorer.
This page covers the tracer that instruments Claude Code itself.
How It Works
The tracer registers Python hook scripts with Claude Code. Claude Code fires named lifecycle events as it processes each prompt; the tracer listens for those events and builds an OpenInference span tree, then exports it to Arthur Engine over OTLP HTTP.
sequenceDiagram
participant Dev as Developer
participant CC as Claude Code
participant Tracer as Arthur Instrumentation
participant AE as Arthur Engine
Dev->>CC: Types a prompt
CC->>Tracer: UserPromptSubmit (turn start, prompt text)
CC->>Tracer: PreToolUse (tool about to run)
CC->>Tracer: PostToolUse (tool succeeded)
CC->>Tracer: PostToolUseFailure (tool failed β error span)
CC->>Tracer: Stop (turn complete)
Tracer->>AE: Export trace (OTLP HTTP)
AE-->>Dev: Trace visible in Arthur UI
Each turn produces a trace rooted at claude-code-turn. The tracer also parses Claude Code's transcript file to extract the actual LLM API calls (model name, timestamps) and attaches them as child spans.
What Gets Traced
Every user prompt becomes a single trace with the following span tree:
Trace: "claude-code-turn" β one per user prompt
βββ LLM claude/claude-sonnet-4-6 β from transcript (actual timestamps)
βββ TOOL Read β PostToolUse (success)
βββ TOOL Edit [ERROR] β PostToolUseFailure (failure)
βββ RETRIEVER WebSearch β PostToolUse, web retrieval
βββ RETRIEVER WebFetch β PostToolUse, web retrieval
βββ AGENT Task β PostToolUse, sub-agent call
| Span kind | Source event | Notes |
|---|---|---|
LLM | Transcript file | Actual model name and timestamps |
TOOL | PostToolUse | File operations, shell commands, etc. |
TOOL [ERROR] | PostToolUseFailure | Failure captured as error span |
RETRIEVER | PostToolUse | WebSearch and WebFetch tool calls |
AGENT | PostToolUse | Sub-agent (Task) invocations |
Traces carry two resource attributes:
arthur.taskβ links the trace to a task in Arthur Enginearthur.sessionβ shared across all turns in one Claude Code session, enabling session-level filtering
Prerequisites
Before you begin, make sure you have:
- Claude Code installed and working locally (or in CI)
- Python 3 available on your
PATH - An Arthur Engine instance with:
- An API key (
GENAI_ENGINE_API_KEY) - A task UUID (
GENAI_ENGINE_TASK_ID) - The traces endpoint URL (
GENAI_ENGINE_TRACE_ENDPOINT)
- An API key (
- Access to the
integrations/claude-codedirectory in thearthur-enginerepository
Clone the Arthur Engine Repository
Both install paths run install.sh from integrations/claude-code inside the arthur-engine repository. Clone it first:
git clone https://github.com/arthur-ai/arthur-engine.git
cd arthur-engineAll commands below assume your working directory is the repository root.
Install Globally
Global install registers the tracer in ~/.claude/settings.json, so it fires in every Claude Code session on your machine regardless of which project you're working in.
cd integrations/claude-code
# Optional but recommended: add credentials so the installer writes the config for you
cp .env.example .env # or create .env manually (see below)
./install.shThe installer performs these steps:
pip install -r requirements.txt- Copies the tracer to
~/.claude/hooks/ - Registers hooks in
~/.claude/settings.json - Writes
~/.claude/arthur_config.jsonfrom.env(if present)
Restart Claude Code after the first install for the hooks to take effect.
install.sh is idempotent β re-running it updates the tracer binary and config without duplicating hook entries.
Install Per-Project
Per-project install scopes tracing to a single project. Hooks are registered in <project>/.claude/settings.local.json, which is gitignored and fires only when Claude Code runs inside that project directory.
cd integrations/claude-code
./install.sh --project-dir path/to/your/projectThe installer performs these steps:
pip install -r requirements.txt- Copies the tracer to
<project>/.claude/hooks/ - Registers hooks in
<project>/.claude/settings.local.json(gitignored β fires only in this project) - Writes
<project>/.claude/arthur_config.jsonfrom.env(project dir or this dir) - Adds
.claude/arthur_config.jsonto<project>/.gitignore
Restart Claude Code after the first install.
Note:
settings.local.jsonuses$CLAUDE_PROJECT_DIRto locate the tracer at runtime, so the same hook registration also works in CI (as long as the tracer is copied into the project β the install step handles this).
Global vs. Per-Project: Which Should I Use?
| Global | Per-Project | |
|---|---|---|
| Scope | All Claude Code sessions | One project only |
| Config file | ~/.claude/settings.json | <project>/.claude/settings.local.json |
| Credentials file | ~/.claude/arthur_config.json | <project>/.claude/arthur_config.json |
| Committed to git | No | No (gitignored) |
| Works in CI | No (no ~ in CI) | Yes |
Configure Credentials
The tracer needs three values to export spans to Arthur Engine. You can supply them in three ways; the tracer resolves them in this priority order:
- Environment variables (
GENAI_ENGINE_API_KEY,GENAI_ENGINE_TASK_ID,GENAI_ENGINE_TRACE_ENDPOINT) <project>/.claude/arthur_config.json~/.claude/arthur_config.json
If none of the above are configured, the tracer silently does nothing β safe to install in shared projects.
Option 1: .env file (recommended for local setup)
.env file (recommended for local setup)Populate .env in the integrations/claude-code directory before running install.sh. The installer reads this file and writes the config automatically.
# integrations/claude-code/.env
GENAI_ENGINE_API_KEY=<your-api-key>
GENAI_ENGINE_TASK_ID=<your-task-id>
GENAI_ENGINE_TRACE_ENDPOINT=https://<your-arthur-engine-host>/api/v1/tracesOption 2: Config file (manual)
You can write either config file directly:
{
"api_key": "<your-api-key>",
"task_id": "<your-task-id>",
"endpoint": "https://<your-arthur-engine-host>/api/v1/traces"
}Place this file at ~/.claude/arthur_config.json for global credentials, or <project>/.claude/arthur_config.json for project-scoped credentials.
Option 3: Environment variables
Set the three variables in your shell or CI environment directly β no config file needed. Environment variables always take precedence over config files.
GitHub Actions Setup
CI is a common tracing use case: you want every automated Claude Code run (PR reviews, issue responses) to appear as a trace in Arthur Engine alongside your local sessions.
Copy the ready-to-use workflow files into your repo's .github/workflows/ directory:
| File | Use case |
|---|---|
workflow-claude-code.yml | Interactive Claude (@claude mentions on issues/PRs) |
workflow-claude-code-review.yml | Automated PR review on every push |
Credentials are passed via GitHub secrets/variables set in the env: block of each workflow.
Step 1: Add secrets and variables to your GitHub repo
Navigate to Settings β Secrets and variables β Actions in your repository and add:
| Type | Name | Value |
|---|---|---|
| Secret | GENAI_ENGINE_API_KEY | Arthur Engine API key |
| Variable | GENAI_ENGINE_TASK_ID | Task UUID in Arthur Engine |
| Variable | GENAI_ENGINE_TRACE_ENDPOINT | https://<host>/api/v1/traces |
Step 2: Copy the workflow files
cp integrations/claude-code/workflow-claude-code.yml \
.github/workflows/workflow-claude-code.yml
cp integrations/claude-code/workflow-claude-code-review.yml \
.github/workflows/workflow-claude-code-review.ymlCommit and push. The workflows reference the secrets and variables you configured in Step 1 via their env: blocks β no further edits are required.
How CI tracing works: The per-project install copies the tracer into
<project>/.claude/hooks/and registers it insettings.local.jsonusing$CLAUDE_PROJECT_DIR. When the workflow checks out your repo and runs Claude Code, the hooks fire and export spans to Arthur Engine using the credentials from environment variables (highest priority).
Verify Traces in Arthur
After completing the install, run a Claude Code session (or trigger a CI workflow) and confirm that traces are appearing in Arthur Engine.
Step 1: Open the Arthur Engine trace explorer
Navigate to your Arthur Engine instance and open the task you configured with GENAI_ENGINE_TASK_ID.
Step 2: Confirm the trace structure
Select a trace. You should see a root span named claude-code-turn with child spans matching the tools Claude invoked during that prompt. Error spans (from PostToolUseFailure) appear highlighted.
Step 3: Filter by session
Use the arthur.session attribute to filter all traces from a single Claude Code session. This lets you reconstruct the full arc of a coding session β every prompt, every tool call, in order.
Troubleshooting
Traces are not appearing in Arthur Engine
Check credentials first. The tracer silently does nothing if credentials are missing. Verify that at least one of the following is configured:
- Environment variables
GENAI_ENGINE_API_KEY,GENAI_ENGINE_TASK_ID,GENAI_ENGINE_TRACE_ENDPOINT <project>/.claude/arthur_config.json~/.claude/arthur_config.json
Also confirm that GENAI_ENGINE_TRACE_ENDPOINT points to https://<your-arthur-engine-host>/api/v1/traces (not the base host URL).
Hooks are not firing
Confirm that you restarted Claude Code after running install.sh. Hook registrations in settings.json or settings.local.json are only read at startup.
For global installs, check that ~/.claude/settings.json contains the hook entries. For per-project installs, check <project>/.claude/settings.local.json.
Per-project install works locally but not in CI
Ensure you are using the per-project install path (--project-dir), not the global path. The global install writes to ~/.claude/, which does not exist in CI environments. The per-project install writes into the project directory, which is available after checkout.
Also confirm that the three environment variables are set in the workflow's env: block and that the corresponding GitHub secrets/variables are configured under Settings β Secrets and variables β Actions.
Duplicate hook entries after re-running install.sh
install.sh is idempotent and should not create duplicates. If you see duplicates, you may have run the installer manually before the idempotency logic was in place. Open ~/.claude/settings.json (or settings.local.json) and remove the duplicate entries, then re-run install.sh.
Running the unit tests
Unit tests cover config discovery, transcript parsing, turn detection, LLM span extraction, all five hook handlers, RETRIEVER span kind routing, and error span emission. No credentials or running services are required β OTLP export is mocked.
cd integrations/claude-code
pip install pytest
python3 -m pytest test_tracer.py -vNext Steps
Now that your Claude Code sessions are flowing into Arthur Engine as traces, you can:
- Prompt Management β version and manage prompts in Arthur, then fetch them at runtime with
arthur.get_prompt() - Continuous Evaluations β set up automated quality checks that run against your traced inferences
- LangChain Integration β if you're using LangChain with OpenAI, Arthur supports
arthur.instrument_langchain()for deeper chain-level tracing - Read our Best Practices for Building Agents Blog Series β observability and tracing fundamentals for building production agents
- Agentic Experiments β run structured experiments against your AI tasks to evaluate performance at scale
Updated about 2 hours ago