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 kindSource eventNotes
LLMTranscript fileActual model name and timestamps
TOOLPostToolUseFile operations, shell commands, etc.
TOOL [ERROR]PostToolUseFailureFailure captured as error span
RETRIEVERPostToolUseWebSearch and WebFetch tool calls
AGENTPostToolUseSub-agent (Task) invocations

Traces carry two resource attributes:

  • arthur.task β€” links the trace to a task in Arthur Engine
  • arthur.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)
  • Access to the integrations/claude-code directory in the arthur-engine repository

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-engine

All 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.sh

The installer performs these steps:

  1. pip install -r requirements.txt
  2. Copies the tracer to ~/.claude/hooks/
  3. Registers hooks in ~/.claude/settings.json
  4. Writes ~/.claude/arthur_config.json from .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/project

The installer performs these steps:

  1. pip install -r requirements.txt
  2. Copies the tracer to <project>/.claude/hooks/
  3. Registers hooks in <project>/.claude/settings.local.json (gitignored β€” fires only in this project)
  4. Writes <project>/.claude/arthur_config.json from .env (project dir or this dir)
  5. Adds .claude/arthur_config.json to <project>/.gitignore

Restart Claude Code after the first install.

Note: settings.local.json uses $CLAUDE_PROJECT_DIR to 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?

GlobalPer-Project
ScopeAll Claude Code sessionsOne 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 gitNoNo (gitignored)
Works in CINo (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:

  1. Environment variables (GENAI_ENGINE_API_KEY, GENAI_ENGINE_TASK_ID, GENAI_ENGINE_TRACE_ENDPOINT)
  2. <project>/.claude/arthur_config.json
  3. ~/.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)

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/traces

Option 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:

FileUse case
workflow-claude-code.ymlInteractive Claude (@claude mentions on issues/PRs)
workflow-claude-code-review.ymlAutomated 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:

TypeNameValue
SecretGENAI_ENGINE_API_KEYArthur Engine API key
VariableGENAI_ENGINE_TASK_IDTask UUID in Arthur Engine
VariableGENAI_ENGINE_TRACE_ENDPOINThttps://<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.yml

Commit 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 in settings.local.json using $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 -v

Next Steps

Now that your Claude Code sessions are flowing into Arthur Engine as traces, you can: