> ## Documentation Index
> Fetch the complete documentation index at: https://arize-ax.mintlify.site/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Claude Code

> Trace Claude Code CLI and Agent SDK sessions, tool usage, and token costs in Arize AX using the Arize Coding Harness Tracing.

> Trace Claude Code CLI sessions, tool usage, and token costs with Arize AX for full observability.

[Claude Code](https://code.claude.com/docs/en/overview) is Anthropic's agentic coding tool that lives in your terminal. The [Arize Coding Harness Tracing](https://github.com/Arize-ai/coding-harness-tracing) instruments sessions using 16 hook events and sends [OpenInference](https://github.com/Arize-ai/openinference) spans to Arize AX. The plugin works with both the Claude Code CLI and the Claude Agent SDK.

<Note>
  **Use this to trace Claude Code (or Agent SDK) *sessions* via the plugin — enabled through a settings file, no in-code instrumentor.** If instead you are **building an application** with the Claude Agent SDK and want standard OpenInference agent, tool, and LLM spans in your app's code, use [Claude Agent SDK](/docs/ax/integrations/python-agent-frameworks/claude-agent-sdk/claude-agent-sdk-tracing) with `ClaudeAgentSDKInstrumentor` instead.
</Note>

## Launch Arize AX

To get started, sign up for a free [Arize AX account](https://app.arize.com/auth/join) and get your Space ID and API Key:

1. Log in at [app.arize.com](https://app.arize.com)
2. Click **Settings** and copy the **Space ID**
3. Open the **API Keys** tab and create or copy an API key

## Install

### Claude Code Marketplace

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
claude plugin marketplace add Arize-ai/coding-harness-tracing
claude plugin install claude-code-tracing@coding-harness-tracing
```

The marketplace flow registers the hooks but skips the interactive wizard, so backend credentials and content-logging preferences must be set directly in `~/.claude/settings.json` under `env` (see [Configuration](#configuration)).

### Curl installer

**macOS / Linux:**

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
curl -sSL https://raw.githubusercontent.com/Arize-ai/coding-harness-tracing/main/install.sh | bash -s -- claude
```

**Windows (PowerShell):**

```powershell theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
iwr -useb https://raw.githubusercontent.com/Arize-ai/coding-harness-tracing/main/install.bat -OutFile $env:TEMP\install.bat
& $env:TEMP\install.bat claude
```

### Local clone

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
git clone https://github.com/Arize-ai/coding-harness-tracing.git
cd coding-harness-tracing
./install.sh claude        # macOS / Linux
install.bat claude         # Windows
```

The curl and local installers write credentials to `~/.arize/harness/config.json` and register the hooks in `~/.claude/settings.json`.

The installer runs a short interactive setup. Every harness in the Arize Coding Harness Tracing repo asks the same questions, in the same order.

### Setup walkthrough

#### 1. Backend selection

Choose where spans are sent:

* **Phoenix** — your own [Phoenix](https://github.com/Arize-ai/phoenix) instance.
* **Arize AX** — the hosted Arize platform.

#### 2. Credentials

The prompts depend on the backend you picked.

<Tabs>
  <Tab title="Arize AX">
    * **API key** — create one on the [API keys](/docs/ax/security-and-settings/api-keys) tab.
    * **Space ID** — shown on the same settings tab as your API keys.
    * **OTLP endpoint** — defaults to `otlp.arize.com:443`. Override it only for a hosted or dedicated instance.
  </Tab>

  <Tab title="Phoenix">
    * **Endpoint** — defaults to `http://localhost:6006`.
    * **API key** — optional. Leave it blank when Phoenix runs without auth.
  </Tab>
</Tabs>

If you have already configured another harness against the same backend, the installer offers a copy-from menu so you can reuse those credentials instead of retyping them.

#### 3. Project name

The project that this harness's spans are grouped under. Defaults to the harness name.

#### 4. User ID (optional)

A free-form identifier attached to every span as `user.id`. Useful when teammates share one backend. Leave it blank to skip.

#### 5. Content logging

Three `[Y/n]` opt-outs that apply to **every** harness, not only the one you are installing:

* Log user prompts?
* Log what tools were asked to do (commands, file paths, URLs)?
* Log what tools returned (file contents, command output)?

You are asked these only the first time you install any harness. Later installs reuse the existing `logging` block in `~/.arize/harness/config.json`, which you can edit at any time.

### Install flags

| Flag                      | Effect                                                                                                                                   |
| :------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------- |
| `--with-skills`           | Symlink this harness's management skill into `.agents/skills/`, so a coding agent in the workspace can manage the tracing config for you |
| `--non-interactive`, `-y` | Ask nothing, and read every value from the environment instead                                                                           |
| `--branch NAME`           | Install from a specific branch instead of `main`                                                                                         |
| `--wheel-dir DIR`         | Install from local wheels in `DIR`: no network access and no remote code execution                                                       |

### Non-interactive install

Pass `--non-interactive` (or `-y`) to skip every prompt above and take each value from the environment instead. Nothing is asked, and a missing required value is an error rather than a prompt, which makes this the mode to use from a script, from CI, or when a coding agent is driving the install itself.

Values come from the environment, or from a dotenv file named explicitly with `ARIZE_ENV_FILE`. Naming a file keeps the API key out of the command line and your shell history.

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
ARIZE_ENV_FILE=~/.arize/onboarding.env ./install.sh <harness> --non-interactive
```

| Variable                              | Default                 | Description                                                                                                                                                                                                                           |
| :------------------------------------ | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ARIZE_API_KEY` + `ARIZE_SPACE_ID`    | —                       | Arize AX credentials. Both are required for the Arize backend.                                                                                                                                                                        |
| `PHOENIX_ENDPOINT`, `PHOENIX_API_KEY` | `http://localhost:6006` | Phoenix endpoint and optional API key.                                                                                                                                                                                                |
| `ARIZE_BACKEND`                       | inferred                | `arize` or `phoenix`. A space ID implies Arize AX and a Phoenix endpoint implies Phoenix. When both are present the install stops and asks you to set this rather than guess, since guessing would discard one backend's credentials. |
| `ARIZE_PROJECT_NAME`                  | harness name            | Project that spans are grouped under. **Read from the dotenv file only.** An installed harness exports its own project name into every session, so an inherited environment value is ignored here.                                    |
| `ARIZE_USER_ID`                       | —                       | Optional `user.id` on every span.                                                                                                                                                                                                     |
| `ARIZE_OTLP_ENDPOINT`                 | `otlp.arize.com:443`    | Override for a hosted or dedicated Arize instance.                                                                                                                                                                                    |
| `ARIZE_LOG_PROMPTS`                   | `false`                 | Set `true` to capture prompt text.                                                                                                                                                                                                    |
| `ARIZE_LOG_TOOL_DETAILS`              | `false`                 | Set `true` to capture tool commands, file paths, and URLs.                                                                                                                                                                            |
| `ARIZE_LOG_TOOL_CONTENT`              | `false`                 | Set `true` to capture tool output.                                                                                                                                                                                                    |
| `ARIZE_ENV_FILE`                      | —                       | Dotenv file to read. No file is read unless this is set, and a path that is not a readable file is an error rather than a fallback to the environment.                                                                                |
| `ARIZE_WHEEL_DIR`                     | —                       | Same as `--wheel-dir`. Install from local wheels instead of downloading the repo.                                                                                                                                                     |

<Warning>
  A named `ARIZE_ENV_FILE` outranks the environment, and there is deliberately **no automatic `./.env` search**. Reading the working directory would let a cloned repository's dotenv choose `ARIZE_OTLP_ENDPOINT` or `PHOENIX_ENDPOINT` while your real credentials came from the environment, installing a config that ships spans and a bearer API key to an endpoint the repo picked, for every later session on that machine. Name the file you mean.
</Warning>

<Note>
  Content logging is **off by default** in this mode, unlike the interactive wizard where each question defaults to yes. A `[Y/n]` default is a person declining to change an answer they were shown; the same default unattended would capture prompts, commands, and file contents that nobody agreed to. Set the `ARIZE_LOG_*` variables you want to `true`.
</Note>

The API key is never echoed. The installer reports only that it found one and where it came from, and every resolved value is reported with its source, so a wrong-credentials install stays diagnosable:

```
[arize] Backend: Arize AX at otlp.arize.com:443 (from default)
[arize]   space ID: my-space (from /path/to/.env)
[arize]   API key: found (from /path/to/.env)
[arize] Project name: codex (from default)
```

### Check what's installed

`status` reports which harnesses are configured and whether their hooks are actually wired into each harness's own settings file. Both have to be true for traces to appear.

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
./install.sh status
./install.sh status --json    # machine-readable
```

`hooks: NOT registered` means credentials are saved but the harness was never wired up, or something removed the hooks. Re-run the install for that harness.

Use `--json` from a script or a coding agent to gate on the exit code without parsing output: `0` means every configured harness is wired up, `1` means nothing is configured, and `2` means at least one harness's hooks are missing. The payload contains no secrets — an API key appears only as `"api_key_present": true` — so it is safe to paste into a bug report.

### Keep it up to date

`update` pulls the latest code and re-registers every harness already in `config.json`.

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
./install.sh update
```

Re-registering runs each harness's installer, so in a terminal it still asks for each project name. With no terminal to answer on, in CI or a cron job, it takes the stored values instead of failing: credentials are not re-read on that path, and the project name keeps whatever is in `config.json`.

## Configuration

The curl and local installers write credentials to `~/.arize/harness/config.json`. Environment variables in `~/.claude/settings.json` take precedence and are required for the marketplace install path.

```json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "env": {
    "ARIZE_API_KEY": "<your-api-key>",
    "ARIZE_SPACE_ID": "<your-space-id>",
    "ARIZE_PROJECT_NAME": "claude-code",
    "ARIZE_TRACE_ENABLED": "true"
  }
}
```

### Redaction controls

Each `ARIZE_LOG_*` flag accepts `"true"` or `"false"` and defaults to `"true"`. Set to `"false"` to opt out per category:

```json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "env": {
    "ARIZE_LOG_PROMPTS": "false",
    "ARIZE_LOG_TOOL_DETAILS": "false",
    "ARIZE_LOG_TOOL_CONTENT": "false"
  }
}
```

| Flag                     | Redacts                                 |
| :----------------------- | :-------------------------------------- |
| `ARIZE_LOG_PROMPTS`      | User prompt and assistant response text |
| `ARIZE_LOG_TOOL_DETAILS` | Tool names and arguments                |
| `ARIZE_LOG_TOOL_CONTENT` | Tool call output content                |

## Observe

Now that you have tracing set up, all Claude Code sessions stream to your Arize AX account for observability and evaluation. You'll see:

* **Turn traces** — each conversation turn (user prompt → assistant response)
* **LLM spans** — Claude's responses with model info and token counts
* **Tool spans** — nested spans for each tool call with inputs, outputs, and duration
* **Subagent spans** — activity from any subagents Claude spawns
* **Session grouping** — all turns from the same session grouped by `session_id`

<Frame caption="Claude Code turns grouped together in a single session view">
  <img src="https://storage.googleapis.com/arize-assets/doc-images/claude-session.png" alt="Claude Code session view in Arize AX showing multiple traces grouped by session ID" />
</Frame>

Drill into any turn trace to inspect the full span tree, including model generations, tool calls, and subagent activity.

<Frame caption="Detailed trace view for a Claude Code turn">
  <img src="https://storage.googleapis.com/arize-assets/doc-images/claude-trace.png" alt="Claude Code trace view in Arize AX showing the trace tree, inputs, outputs, and nested spans" />
</Frame>

### Hooks Captured

| Hook                  | Span Kind | Description                                                                     |
| :-------------------- | :-------- | :------------------------------------------------------------------------------ |
| `SessionStart`        | CHAIN     | Session initialized, trace/tool counters reset                                  |
| `UserPromptSubmit`    | CHAIN     | User prompt captured (also lazy-inits session for SDK)                          |
| `UserPromptExpansion` | —         | Stashes slash command metadata to attach to the next turn span                  |
| `PreToolUse`          | TOOL      | Tool invocation started, records tool name and input                            |
| `PostToolUse`         | TOOL      | Tool invocation completed, records output and duration                          |
| `PostToolUseFailure`  | TOOL      | Tool invocation failed, records error attributes                                |
| `Stop`                | LLM       | Model response completed with input/output values                               |
| `StopFailure`         | LLM       | Turn failed before completion, emits a span with `error.type` / `error.message` |
| `SubagentStart`       | —         | Records subagent start time and prompt keyed by `agent_id`                      |
| `SubagentStop`        | CHAIN     | Subagent response completed                                                     |
| `PreCompact`          | —         | Records compaction start time and trigger                                       |
| `PostCompact`         | CHAIN     | Compaction completed, emits a span when it fires inside a turn                  |
| `PermissionRequest`   | CHAIN     | Permission prompt for tool use                                                  |
| `PermissionDenied`    | CHAIN     | Auto-mode tool denial recorded inside the current turn                          |
| `Notification`        | CHAIN     | System notification event                                                       |
| `SessionEnd`          | CHAIN     | Session teardown, state file cleanup                                            |

Hooks marked `—` don't emit their own span — they stash state that an adjacent hook (Turn, tool, or compaction) attaches when it fires.

## Agent SDK Setup

The tracing plugin also works with the [Claude Agent SDK](https://platform.claude.com/docs/en/agent-sdk/overview) in both Python and TypeScript. The SDK loads the plugin locally — no marketplace install is required — but the setup must be done in your application code before the SDK session starts, so the agent cannot configure it at runtime.

<Callout type="warning">
  You must use `ClaudeSDKClient`. The standalone `query()` function does not support hooks, so tracing will not work with it.
</Callout>

### 1. Locate the plugin

The plugin path depends on how you installed the harness:

* **Installed via the Claude Code CLI marketplace:** the plugin is cached at `~/.claude/plugins/cache/coding-harness-tracing/claude-code-tracing/1.0.0`.
* **Installed via the curl or local installer:** the plugin lives at `~/.arize/harness/tracing/claude_code`.
* **Not installed:** clone the repo into your project — the plugin path is `./coding-harness-tracing/claude-code-tracing`:

  ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  git clone https://github.com/Arize-ai/coding-harness-tracing.git
  ```

### 2. Create a settings file

The SDK spawns a Claude Code subprocess that does not inherit your shell environment, so tracing env vars must be passed through a settings file referenced from `ClaudeAgentOptions`:

```json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "env": {
    "ARIZE_TRACE_ENABLED": "true",
    "ARIZE_API_KEY": "<your-api-key>",
    "ARIZE_SPACE_ID": "<your-space-id>",
    "ARIZE_PROJECT_NAME": "claude-code"
  }
}
```

The same `ARIZE_LOG_*` redaction flags from [Configuration](#configuration) apply here.

### 3. Wire the plugin into your app

Pass the plugin path and settings file to `ClaudeSDKClient`:

<Tabs>
  <Tab title="Python">
    ```python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
    from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient

    PLUGIN_PATH = "./coding-harness-tracing/claude-code-tracing"  # or your install path

    options = ClaudeAgentOptions(
        plugins=[{"type": "local", "path": PLUGIN_PATH}],
        settings="./settings.local.json",
    )

    async with ClaudeSDKClient(options=options) as client:
        await client.query("Your prompt here")
        async for message in client.receive_response():
            print(message)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
    import { ClaudeSDKClient } from "@anthropic-ai/claude-agent-sdk";

    const PLUGIN_PATH = "./coding-harness-tracing/claude-code-tracing"; // or your install path

    const client = new ClaudeSDKClient({
      plugins: [{ type: "local", path: PLUGIN_PATH }],
      settings: "./settings.local.json",
    });

    await client.connect();
    await client.query("Your prompt here");
    for await (const message of client.receiveResponse()) {
      console.log(message);
    }
    await client.close();
    ```
  </Tab>
</Tabs>

If you installed via the curl or local installer, the harness ships a Python convenience helper that returns a pre-configured `ClaudeAgentOptions` (plugin path + `setting_sources=["user"]` so user-level Claude settings are honored):

```python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
from tracing.claude_code.agent_sdk import claude_options

async with ClaudeSDKClient(options=claude_options()) as client:
    ...
```

### Validate

Add `"ARIZE_DRY_RUN": "true"` to your settings file to verify hooks fire without sending data, and tail `~/.arize/harness/logs/claude-code.log` to confirm activity.

### Hook parity

| SDK            | Coverage                                                                                                                                                                                                                      |
| :------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **TypeScript** | Full parity — all 16 hooks fire, including `SessionStart`, `Notification`, `PermissionRequest`, and `SessionEnd`.                                                                                                             |
| **Python**     | `SessionStart`, `SessionEnd`, `Notification`, and `PermissionRequest` are not available. Session state is lazily initialized on the first `UserPromptSubmit`; core tracing (LLM, tool, and subagent spans) still works fully. |

## Reference

For the full list of environment variables, default file paths, and troubleshooting steps, see the [Claude Code tracing README](https://github.com/Arize-ai/coding-harness-tracing/blob/main/tracing/claude_code/README.md).

## Uninstall

**Marketplace install:**

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
claude plugin uninstall claude-code-tracing@coding-harness-tracing
claude plugin marketplace remove Arize-ai/coding-harness-tracing
```

**Curl or local install:**

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
curl -sSL https://raw.githubusercontent.com/Arize-ai/coding-harness-tracing/main/install.sh | bash -s -- uninstall claude
```

## Resources

<CardGroup>
  <Card icon="github" href="https://github.com/Arize-ai/coding-harness-tracing" title="Arize Coding Harness Tracing" horizontal />

  <Card icon="github" href="https://github.com/Arize-ai/openinference" title="OpenInference" horizontal />

  <Card icon="book-open" href="https://code.claude.com/docs/en/overview" title="Claude Code Documentation" horizontal />

  <Card icon="book-open" href="https://code.claude.com/docs/en/plugins" title="Claude Code Plugins" horizontal />

  <Card icon="book-open" href="https://arize.com/blog/claude-md-best-practices-learned-from-optimizing-claude-code-with-prompt-learning/" title="CLAUDE.md: Best Practices for Optimizing Claude Code with Prompt Learning" horizontal />
</CardGroup>
