/opik-explain
Root-cause a specific Opik trace, or a pattern across traces, and return a grounded explanation. Uses the hosted Opik MCP when it is connected, and falls back to SDK scripting otherwise. Returns the root cause, the evidence spans as clickable Opik UI links, and one suggested
Claude Code
$ npx -y skills add comet-ml/opik-claude-code-plugin --skill opik-explain --agent claude-code
Codex
$ npx skills add comet-ml/opik-claude-code-plugin --skill opik-explain -a codex -y
Cursor
$ npx skills add comet-ml/opik-claude-code-plugin --skill opik-explain -a cursor -y
Gemini CLI
$ npx skills add comet-ml/opik-claude-code-plugin --skill opik-explain -a gemini-cli -y
GitHub Copilot
$ npx skills add comet-ml/opik-claude-code-plugin --skill opik-explain -a github-copilot -y
OpenCode
$ npx skills add comet-ml/opik-claude-code-plugin --skill opik-explain -a opencode -y
Windsurf
$ npx skills add comet-ml/opik-claude-code-plugin --skill opik-explain -a windsurf -y
SKILL.md
opik-explain.SKILL.mdname: opik-explain
description: Root-cause a specific Opik trace, or a pattern across traces, and return a grounded explanation. Uses the hosted Opik MCP when it is connected, and falls back to SDK scripting otherwise. Returns the root cause, the evidence spans as clickable Opik UI links, and one suggested next step. Use for "why did this trace fail", "explain this trace", "debug this trace", "why is my agent slow or wrong". Not for adding tracing to an app (use the instrument skill) or for changing code.
compatibility: Tested with Claude Code; works with any Agent Skills-compatible host (Cursor, VS Code Copilot, Codex). Requires a Python or TypeScript project with Opik configured and at least one trace. Install the `opik` skill alongside this one — it holds the shared SDK and observability references; without it, this skill falls back to the public docs.
allowed-tools:
- Read
- Grep
- Glob
- Bash
metadata:
last_updated: "2026-09-08"
source_commit: "2.0.0"
argument-hint: "[trace id, or a description of the behavior to explain]"
Explain — Root-Cause an Opik Trace and Ground It in the Code
**Definition of done:** a grounded root cause for the requested trace (or pattern), tied to **specific evidence spans** and paired with **exactly one** suggested next step. "Grounded" means the explanation names the failing/anomalous span and connects it to the code or data that produced it — not a restatement of the trace. If the target can't be fetched or read, stop at the **first** genuine blocker and return one concrete next step. A trace dump is not an explanation.
Operate: **investigate over the real trace data, reason against the repo, commit to a single most-likely root cause with its evidence — and change no code.** This skill is read-only by design.
Inputs
The entry point is `/opik-explain <trace-id>` (one trace) or `/opik-explain <describe the behavior>` (a pattern to find and explain). Infer the rest; treat these as **optional overrides**:
- project name (default: inferred from config/repo) · time window for a pattern (default: recent) · a known-good trace to compare against.
Ask only at a genuine, non-inferable blocker (see **Blockers**).
Activation — the only in-scope work
1. Resolve the target
- A **trace id** (uuid-shaped): explain that one trace.
- A **behavior/pattern** ("hallucinations since the prompt change", "slow responses"): search for the matching set (below), then explain the shared cause.
- Confirm Opik is reachable: if `~/.opik.config` exists or `OPIK_API_KEY` is set, use it. Otherwise → **Blocker** ("run `opik configure`, then rerun").
2. Fetch the trace and spans — MCP first, SDK fallback
Check whether the hosted Opik MCP is connected and prefer it; fall back to SDK scripting when it isn't.
- **MCP connected:** use the MCP to `read` the trace and `list`/`read` its spans.
- **No MCP:** fall back to the SDK.
Either way, read every span's input/output/error/duration.
import opik
client = opik.Opik()
tid = "<trace_id>"
trace = client.get_trace_content(tid) # TracePublic: exposes project_id, input, output, error info — NOT project_name (accessing .project_name raises)
project = client.rest_client.projects.get_project_by_id(trace.project_id).name
spans = client.search_spans(project_name=project, trace_id=tid) # spans come from a SEPARATE call, not from the trace object
# ALWAYS pass project_name: without it the SDK searches the configured default project, which
# returns an empty list (or a 404 if that project doesn't exist) even for a valid trace id.
# Reconstruct the tree via each span's parent_span_id (the root span has none).
# Your anchor is the first span that errored, returned wrong output, or dominates the duration.
For a **pattern**, pull the matching set scoped to the project, then look for the shared failing span across them. With the MCP, one `list` call does the filtering and ordering server-side — `filters` is an OQL string, `sort` is `"<field> [asc|desc]"`, `since` takes `"1h"` / `"7d"`:
list(entity_type="trace", project_name="<project>", since="7d",
filters="error_info is_not_empty", sort="start_time desc") # error traces
list(entity_type="trace", project_name="<project>", since="7d",
sort="duration desc") # slow traces (duration in ms)
list(entity_type="trace", project_name="<project>", since="7d",
filters="feedback_scores.hallucination > 0.5") # low-scored traces
list(entity_type="span", project_name="<project>", since="7d",
filters='name = "<span name>" AND error_info is_not_empty') # the shared failing span, across tracesThe applied filter is echoed on the first line; a rejected one comes back with what fixes it (`schema("list.trace")` is the full field reference). Without the MCP, the SDK takes the same grammar:
traces = client.search_traces(project_name="<project>", filter_string="error_info is_not_empty")
Traces are asynchronous; if you just produced the trace, allow a few seconds and confirm the flush ran.
3. Root-cause it
The coding agent root-causes over the fetched data, against the repo — it has the one thing a generic reasoner lacks: **the code**. Whether the trace came from the MCP or the SDK, the analysis is the same: find the anchor span (error / wrong output / latency dominator), read its input and output, connect it to the code (grep the repo for the span name / function), and state the **single most-likely root cause** with its evidence spans. Prefer one well-evidenced cause over a list of maybes.
4. Report
Return the root cause, the evidence spans **as clickable Opik UI links** (the trace redirect URL Opik emits, e.g. `.../session/redirect/...?trace_id=THE_ID` — never a bare id), and one next step (see **Output**). If a fix is obvious, name it as the next step; do not apply it (this skill changes no code — handing off to `opik-instrument`/`o
Read more
name: opik-explain description: Root-cause a specific Opik trace, or a pattern across traces, and return a grounded explanation. Uses the hosted Opik MCP when it is connected, and falls back to SDK scripting otherwise. Returns the root cause, the evidence spans as clickable Opik UI links, and one suggested next step. Use for "why did this trace fail", "explain this trace", "debug this trace", "why is my agent slow or wrong". Not for adding tracing to an app (use the instrument skill) or for changing code. compatibility: Tested with Claude Code; works with any Agent Skills-compatible host (Cursor, VS Code Copilot, Codex). Requires a Python or TypeScript project with Opik configured and at least one trace. Install the `opik` skill alongside this one — it holds the shared SDK and observability references; without it, this skill falls back to the public docs. allowed-tools: - Read - Grep - Glob - Bash metadata: last_updated: "2026-09-08" source_commit: "2.0.0" argument-hint: "[trace id, or a description of the behavior to explain]"
Explain — Root-Cause an Opik Trace and Ground It in the Code
**Definition of done:** a grounded root cause for the requested trace (or pattern), tied to **specific evidence spans** and paired with **exactly one** suggested next step. "Grounded" means the explanation names the failing/anomalous span and connects it to the code or data that produced it — not a restatement of the trace. If the target can't be fetched or read, stop at the **first** genuine blocker and return one concrete next step. A trace dump is not an explanation.
Operate: **investigate over the real trace data, reason against the repo, commit to a single most-likely root cause with its evidence — and change no code.** This skill is read-only by design.
Inputs
The entry point is `/opik-explain <trace-id>` (one trace) or `/opik-explain <describe the behavior>` (a pattern to find and explain). Infer the rest; treat these as **optional overrides**:
- project name (default: inferred from config/repo) · time window for a pattern (default: recent) · a known-good trace to compare against.
Ask only at a genuine, non-inferable blocker (see **Blockers**).
Activation — the only in-scope work
1. Resolve the target
- A **trace id** (uuid-shaped): explain that one trace.
- A **behavior/pattern** ("hallucinations since the prompt change", "slow responses"): search for the matching set (below), then explain the shared cause.
- Confirm Opik is reachable: if `~/.opik.config` exists or `OPIK_API_KEY` is set, use it. Otherwise → **Blocker** ("run `opik configure`, then rerun").
2. Fetch the trace and spans — MCP first, SDK fallback
Check whether the hosted Opik MCP is connected and prefer it; fall back to SDK scripting when it isn't.
- **MCP connected:** use the MCP to `read` the trace and `list`/`read` its spans.
- **No MCP:** fall back to the SDK.
Either way, read every span's input/output/error/duration.
import opik client = opik.Opik() tid = "<trace_id>" trace = client.get_trace_content(tid) # TracePublic: exposes project_id, input, output, error info — NOT project_name (accessing .project_name raises) project = client.rest_client.projects.get_project_by_id(trace.project_id).name spans = client.search_spans(project_name=project, trace_id=tid) # spans come from a SEPARATE call, not from the trace object # ALWAYS pass project_name: without it the SDK searches the configured default project, which # returns an empty list (or a 404 if that project doesn't exist) even for a valid trace id. # Reconstruct the tree via each span's parent_span_id (the root span has none). # Your anchor is the first span that errored, returned wrong output, or dominates the duration.
For a **pattern**, pull the matching set scoped to the project, then look for the shared failing span across them. With the MCP, one `list` call does the filtering and ordering server-side — `filters` is an OQL string, `sort` is `"<field> [asc|desc]"`, `since` takes `"1h"` / `"7d"`:
list(entity_type="trace", project_name="<project>", since="7d",
filters="error_info is_not_empty", sort="start_time desc") # error traces
list(entity_type="trace", project_name="<project>", since="7d",
sort="duration desc") # slow traces (duration in ms)
list(entity_type="trace", project_name="<project>", since="7d",
filters="feedback_scores.hallucination > 0.5") # low-scored traces
list(entity_type="span", project_name="<project>", since="7d",
filters='name = "<span name>" AND error_info is_not_empty') # the shared failing span, across tracesThe applied filter is echoed on the first line; a rejected one comes back with what fixes it (`schema("list.trace")` is the full field reference). Without the MCP, the SDK takes the same grammar:
traces = client.search_traces(project_name="<project>", filter_string="error_info is_not_empty")
Traces are asynchronous; if you just produced the trace, allow a few seconds and confirm the flush ran.
3. Root-cause it
The coding agent root-causes over the fetched data, against the repo — it has the one thing a generic reasoner lacks: **the code**. Whether the trace came from the MCP or the SDK, the analysis is the same: find the anchor span (error / wrong output / latency dominator), read its input and output, connect it to the code (grep the repo for the span name / function), and state the **single most-likely root cause** with its evidence spans. Prefer one well-evidenced cause over a list of maybes.
4. Report
Return the root cause, the evidence spans **as clickable Opik UI links** (the trace redirect URL Opik emits, e.g. `.../session/redirect/...?trace_id=THE_ID` — never a bare id), and one next step (see **Output**). If a fix is obvious, name it as the next step; do not apply it (this skill changes no code — handing off to `opik-instrument`/`o
Log Claude Code sessions to Opik for LLM observability, plus skills and agents for building observable AI applications. Opik is the open-source LLM observability and evaluation platform, built by Comet.
Other skills on opik.
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
Questions about opik-explain.
- Which coding agents can use opik-explain?
- opik-explain installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

