npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@soulerou/opik-claudecode

v0.1.1

Published

Opik observability for Claude Code via SessionEnd hook — auto-reports every session to your Opik server, with project name auto-derived from <gitRepoName>-<branch>.

Readme

opik-claudecode

Opik observability for Claude Code — every session is automatically reported to your Opik server, with the project name auto-derived from <gitRepoName>-<branch>.

npm License Node Python

npm install -g @soulerou/opik-claudecode
opik-claudecode                  # interactive setup, then you're done

After setup, every Claude Code session you run anywhere on this machine is reported to Opik on session exit — LLM rounds, tool calls, sidechain (subagent) activity, token usage. You don't change how you use Claude Code; the data just shows up.

This is the Claude Code counterpart to opik-opencode. Same data model, simpler runtime — Claude Code's SessionEnd hook invokes a single zero-dep Python script that reads the transcript and POSTs to the Opik REST API.


Contents


Why

You run Claude Code daily. You'd like to:

  • See which sessions consumed how many tokens, in which repo, on which branch
  • Replay what tools Claude actually invoked, with what inputs and outputs
  • Compare conversations across branches or repos to spot regressions or cost spikes
  • Build evals against real session transcripts later

Opik gives you all of that, but Claude Code by itself doesn't ship transcripts to anywhere. This package wires the two together — a single SessionEnd hook + a small Python script — and gets out of your way.

Quick start

1. Install

npm install -g @soulerou/opik-claudecode

Or zero-install via npx (downloads once, cached after):

npx @soulerou/opik-claudecode

You'll see a one-line banner: Run opik-claudecode to configure. That's the only thing npm install does on its own — no user files are written, no prompts in CI, no surprises.

The package is published under the @soulerou scope, but the bin command after install is the unscoped opik-claudecode (npm uses the bin field, which is independent of the package name).

2. Configure

opik-claudecode

The interactive wizard asks two things:

  1. Scopeuser (recommended; covers every session on this machine) or project (only this directory).
  2. Server config — Opik URL/IP+port, workspace, and an optional API key.

It then:

  • copies the report script into ~/.opik-claudecode/ (or ./.opik-claudecode/ for project scope)
  • writes your server config next to it
  • merges a SessionEnd hook into ~/.claude/settings.json (or ./.claude/settings.json), preserving any existing hooks

3. Use Claude Code as usual

cd <any-git-repo>
claude               # have a conversation
/exit                # SessionEnd fires → script runs → Opik gets the data

That's it. Your session shows up in the Opik project named <repoName>-<branch>.

To check things are wired correctly:

opik-claudecode status
tail -n 5 ~/.opik-claudecode/log/report.log

What you get in Opik

Data model — Claude Code session → Opik Thread → Trace → Spans

Interactive SVG: docs/diagrams/data-model.html

Each Claude Code session lands as one Trace with structured spans inside it:

🧵 Thread: <sessionId>                            ← Threads view in Opik
│
📦 claudecode-<first user message>                ← Trace (thread_id = sessionId)
│
├─ 🤖 claude-opus-4-7 #1                          ← LLM Span (round 1)
│   ├─ Input  : "I want to refactor the auth flow…"
│   ├─ Output : "Let me start by reading the current login code…"
│   └─ Tokens : input=8 462, output=312
│
├─ 🔧 tool:Read                                   ← Tool Span
│   ├─ Args   : { file_path: "src/auth/login.ts" }
│   └─ Output : "import React from …"
│
├─ 🤖 claude-opus-4-7 #2                          ← LLM Span (round 2)
│   └─ Output : "I see the issue. We can simplify by …"
│
├─ 🔧 tool:Edit                                   ← Tool Span
│   ├─ Args   : { file_path: "src/auth/login.ts", old_string: …, new_string: … }
│   └─ Output : "Edit successful."
│
└─ 🤖 claude-opus-4-7 #3                          ← LLM Span (round 3)
    └─ Output : "Done — the refactor is in place."

| Claude Code | Opik object | |---|---| | One session | One Trace (thread_id = sessionId, name = claudecode-<first user msg>) | | One assistant turn | One LLM Span (name = <model> #<round>, with token usage) | | One tool call (Bash, Read, Edit, Task, …) | One Tool Span (name = tool:<name>, with args + output) | | Sidechain / subagent records | Span tagged sidechain (sibling of the parent trace, not nested) |

Same session data is also accessible at the Threads view (Opik aggregates traces by thread_id), so you can scrub through one Claude Code session as a unified timeline.

How it works

Runtime flow — SessionEnd hook spawns report_session.py which POSTs to Opik

Interactive SVG: docs/diagrams/runtime-flow.html

┌──────────────────────────────────────────────────────────────┐
│  Claude Code session                                         │
│                                                              │
│  user → assistant → tool calls → assistant → … → /exit       │
│                                                       │      │
│                                            SessionEnd hook   │
│                                                       │      │
│                                                       ▼      │
│                                python3 report_session.py     │
│                                       │                      │
│  reads stdin payload + transcript JSONL                      │
│  resolves project = <gitRepoName>-<branch>                   │
│  builds 1 Trace + N LLM spans + N tool spans                 │
│       │                                                      │
└───────┼──────────────────────────────────────────────────────┘
        │
        ▼  POST /v1/private/traces/batch
        ▼  POST /v1/private/spans/batch
   ┌─────────────────┐
   │   Opik server   │  (Cloud / self-hosted / local)
   └─────────────────┘
  • One-shot, end-of-session — the script only runs when Claude Code's SessionEnd hook fires (i.e. on /exit, session resume, or process termination). It reads the entire transcript at transcript_path, builds the Opik payload, and POSTs in two batched calls.
  • Idempotent — trace and span IDs are deterministic UUID v7s derived from session_id and per-record UUIDs, so re-running the script for the same session produces the same identifiers (server-side dedup safe).
  • Never blocks Claude — telemetry errors are logged to ~/.opik-claudecode/log/report.log and the script always exits 0.
  • Zero runtime deps — only python3 (3.8+) and Node stdlib. The Python script uses only urllib/json/subprocess from stdlib.

Configuration

Resolution order (high → low)

  1. CLI flags — --api-url, --api-key, --workspace, --project (only when running report_session.py ad-hoc)
  2. Env vars — OPIK_API_URL, OPIK_API_KEY, OPIK_WORKSPACE_NAME, OPIK_PROJECT_NAME
  3. Project-level config — <repo>/.opik-claudecode/opik-claudecode.json
  4. User-level config — ~/.opik-claudecode/opik-claudecode.json
  5. Defaults — apiUrl=http://localhost:5173/api, workspaceName=default, projectName=auto

Config file shape

Both user-level and project-level files share the same shape (opik-claudecode.json):

{
  "apiUrl": "http://10.101.102.98:80/api",
  "apiKey": "",
  "workspaceName": "default",
  "projectName": null
}

| Field | Default | Notes | |---|---|---| | apiUrl | http://localhost:5173/api | Opik REST endpoint base. Include /api when self-hosted. | | apiKey | "" | Leave blank for self-hosted. Set for Opik Cloud. | | workspaceName | "default" | Sent in the Comet-Workspace header. | | projectName | null | null = auto-derive <gitRepoName>-<branch>. Set explicitly to override. |

Non-interactive setup

For dotfiles, container images, or provisioning scripts:

OPIK_API_URL=http://10.101.102.98:80/api \
OPIK_WORKSPACE_NAME=default \
OPIK_API_KEY= \
opik-claudecode --user --yes

--yes skips the interactive scope picker and uses defaults / env vars for everything.

CLI reference

opik-claudecode [setup]              interactive setup (default)
opik-claudecode [setup] --user       non-interactive user-scope install
opik-claudecode [setup] --project    non-interactive project-scope install
opik-claudecode status [--user|--project]
opik-claudecode uninstall [--user|--project]
opik-claudecode help

| Verb | What it does | |---|---| | setup (default) | Copy script + write config + merge hook. Idempotent — re-running won't duplicate the hook entry. | | status | Show install dir, script presence, parsed config, hook wiring — for both scopes by default. | | uninstall | Remove only our SessionEnd hook entry from settings.json. Preserves all other hooks. Leaves the install dir intact. | | help | This usage page. |

| Flag | Meaning | |---|---| | --user | Skip scope prompt; target ~/.claude/settings.json and ~/.opik-claudecode/. | | --project | Skip scope prompt; target ./.claude/settings.json and ./.opik-claudecode/. | | --yes, -y | Non-interactive — use env vars / existing config / defaults. | | --help, -h | Show usage. |

Project naming

The default project name is derived from your git context, at session-end time:

project_name = "<gitRepoName>-<branch>"

| Where | How it's resolved | |---|---| | gitRepoName | basename(git rev-parse --show-toplevel) from the session's cwd | | branch | git rev-parse --abbrev-ref HEAD from cwd (live, authoritative) | | Fallback | Transcript's recorded gitBranch field — only used if the live call fails | | Outside a repo | Literal fallback: claudecode |

Slashes in branch names are normalized to - (so feature/foofeature-foo). Detached HEAD shows up as <repo>-HEAD.

To override: set OPIK_PROJECT_NAME=my-name in your env, or set "projectName": "my-name" in opik-claudecode.json.

Re-uploading old sessions

The report script can be invoked manually for any past session — useful for backfilling, or for testing the wiring without doing a real Claude run:

python3 ~/.opik-claudecode/report_session.py \
    --session <session-id> \
    --transcript ~/.claude/projects/-Users-…/<id>.jsonl \
    --cwd /path/to/the/repo \
    --api-url http://10.101.102.98:80/api \
    --project my-project

--dry-run prints the would-be JSON payload to stdout without posting:

python3 ~/.opik-claudecode/report_session.py \
    --session abc --transcript /path/to/transcript.jsonl \
    --cwd /tmp/repo --dry-run | jq .trace.name

Re-running for the same session_id is idempotent at the ID level — trace/span IDs are deterministic, so the server sees the same identifiers each time.

Troubleshooting

Nothing appears in Opik after /exit

opik-claudecode status              # is the hook wired?
tail -n 30 ~/.opik-claudecode/log/report.log

The log will contain the full sync line (sync session=… project=… spans=… endpoint=…) and any HTTP error from the POST. Common causes:

  • apiUrl typo — double-check the host/port; for self-hosted Opik the path is …/api, not the UI URL.
  • Workspace mismatch — Cloud users must set workspaceName (Cloud rejects requests without Comet-Workspace).
  • Network unreachable — log shows network: <reason>. Check firewalls, VPN, etc.

Hook fires, but the project is named claudecode instead of <repo>-<branch>

The script ran outside a git repo (or from a path where git rev-parse failed). Verify with:

git -C "$PWD" rev-parse --show-toplevel
git -C "$PWD" rev-parse --abbrev-ref HEAD

If both succeed but the project still falls back, set OPIK_PROJECT_NAME explicitly or hard-code projectName in the config file.

python3: command not found

The hook command is python3 …. Either install Python 3.8+ and put it on the PATH, or edit the hook in settings.json to point at a specific interpreter (e.g. /opt/homebrew/bin/python3 on macOS).

Sessions on different machines should report to the same Opik project

Set projectName explicitly in opik-claudecode.json (or OPIK_PROJECT_NAME in your shell rc) so all machines agree on the name regardless of their local git layout.

Limitations

  • Not streaming. Sessions appear in Opik only after SessionEnd. If Claude Code crashes hard before exit, that session is not reported.
  • Cache-token detail not aggregated at trace level. Per-span metadata.usage carries the full cache_creation_input_tokens / cache_read_input_tokens values — but trace totals only sum input_tokens + output_tokens. Add up cache fields yourself in the Opik UI if needed.
  • Sidechain spans aren't nested. Subagent records are tagged sidechain and emitted as siblings under the parent trace, not nested under a single subagent parent span. Good enough for filtering and grouping.
  • Light payload sanitization. Each text field is truncated to 16 KiB. There's no keyword-based redaction yet — don't run this against sessions where Claude saw real production secrets unless your Opik instance is trusted.

Server endpoints used

Verified against the Opik REST API docs:

| Path | Method | Body | Used for | |---|---|---|---| | /v1/private/traces/batch | POST | {"traces":[…]} | The single session-level trace | | /v1/private/spans/batch | POST | {"spans":[…]} | LLM + tool spans, batched 100 per request |

Headers (set when configured):

Content-Type:    application/json
Authorization:   <api-key>
Comet-Workspace: <workspace>

Field naming throughout is snake_case (thread_id, start_time, project_name, trace_id).

Layout

opik-claudecode/
├── README.md                                 # this file
├── package.json                              # npm metadata; bin → bin/opik-claudecode.js
├── install.sh                                # bash installer (alternative to the npm CLI)
├── bin/
│   ├── opik-claudecode.js                    # CLI entry — pure Node stdlib
│   └── postinstall-banner.js                 # one-line note after `npm install` (silent in CI)
├── lib/
│   └── setup.js                              # setup / status / uninstall logic
├── scripts/
│   └── report_session.py                     # the script the hook runs (zero-dep, Python 3.8+)
├── templates/
│   ├── settings.hooks.user.template.json     # SessionEnd hook fragment (user scope)
│   ├── settings.hooks.project.template.json  # SessionEnd hook fragment (project scope)
│   └── opik-claudecode.example.json          # config-file example
└── docs/
    └── diagrams/
        ├── runtime-flow.html                 # dark-themed SVG of the runtime flow
        ├── runtime-flow.preview.webp         # raster preview (embedded in README)
        ├── data-model.html                   # dark-themed SVG of the data-model mapping
        └── data-model.preview.webp           # raster preview (embedded in README)

Manual install (no npm, no install.sh)

If you'd rather wire it yourself, copy scripts/report_session.py somewhere stable and add this to ~/.claude/settings.json:

{
  "hooks": {
    "SessionEnd": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 /absolute/path/to/report_session.py",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

The script reads its server config from env vars (OPIK_API_URL, OPIK_API_KEY, OPIK_WORKSPACE_NAME, OPIK_PROJECT_NAME) or from ~/.opik-claudecode/opik-claudecode.json. Pick whichever you prefer.

License

Apache-2.0