oodle-traces

Finds and examines detailed records of what your software did, so you can spot slow parts and fix problems.

Installation
Run `npx skills add "https://github.com/oodle-ai/agent-skills" --skill "oodle-traces"` to install this skill, then follow its SKILL.md instructions for my next request.

Paste this into Claude Code, Cursor, or any agent that can run commands.

SKILL.mdShow the author's original SKILL.md
---
name: oodle-traces
description: Search and analyze Oodle traces by service, operation, duration, and error status, and run TraceQL searches and metrics.
metadata:
  version: "1.1.0"
  author: oodle-ai
  repository: https://github.com/oodle-ai/agent-skills
  tags: oodle,traces,spans,opentelemetry,apm,traceql
  globs: "**/*trace*,**/*span*,**/*otel*"
  alwaysApply: "false"
---

# Oodle Traces — Search and Analysis

This skill teaches the agent to search Oodle traces with the right service / time / duration filters, to fetch full trace detail by ID, and to run TraceQL.

## Prerequisites

```bash
brew install oodle-ai/oodle/oodle
oodle configure
```

Confirm the traces endpoint works (use a recent time window):

```bash
oodle traces list --start -15m --end now -o json | jq 'length'
```

## Command Execution Order

Before running any oodle command:
1. Check whether a service name and approximate time window are already in context.
2. If not, run `oodle traces label-values resource::service.name --start -1h --end now -o json` to discover services that emitted traces recently.
3. Build the search with `--service`, `--start`, `--end` and at least one of `--tags '{"span_status":"Error"}'` / `--min-duration` / `--operation`. For conditions on any span attribute, use `oodle traces traceql search`.
4. To inspect a single trace, run `oodle traces get <trace-id> --start -1h --end now -o json`.
5. Do not run `oodle traces list` without a `--service` filter on production environments.

## Quick Reference

| Task | Command |
|------|---------|
| List traces in a service | `oodle traces list --service <svc> --start -1h --end now -o json` |
| List error traces only | `oodle traces list --service <svc> --tags '{"span_status":"Error"}' --start -1h --end now -o json` |
| List slow traces (>500ms) | `oodle traces list --service <svc> --min-duration 500ms --start -1h --end now -o json` |
| Filter by operation | `oodle traces list --service <svc> --operation GET_/users --start -1h --end now -o json` |
| Get full trace | `oodle traces get <trace-id> --start -1h --end now -o json` |
| TraceQL search | `oodle traces traceql search '{ status = error }' --start -1h --end now` |
| TraceQL metrics | `oodle traces traceql metrics '{ status = error } \| rate() by (resource.service.name)' --start -1h --end now --step 1m` |

## Common Operations

### Searching for errors in the last hour

```bash
# ✅ CORRECT — scoped to one service, narrow time window, error filter on
oodle traces list --service api --tags '{"span_status":"Error"}' --start -1h --end now -o json

# ❌ WRONG — no service, no error filter, huge result set
oodle traces list --start -1h --end now -o json
```

### Searching for slow traces

```bash
# ✅ CORRECT — slow checkout traces in the last hour
oodle traces list --service checkout --min-duration 500ms --start -1h --end now -o json

# ✅ CORRECT — narrow further by operation when you know the endpoint
oodle traces list --service checkout --operation POST_/cart --min-duration 500ms --start -1h --end now -o json

# ❌ WRONG — listing everything and sorting client-side wastes the API budget
oodle traces list --service checkout --start -1h --end now -o json | jq '.data | sort_by(.spans[0].duration) | reverse | .[0:10]'
```

### Fetching a full trace

```bash
# ✅ CORRECT — list to discover trace ids, then get the one of interest
TRACE_ID=$(oodle traces list --service api --tags '{"span_status":"Error"}' --start -15m --end now -o json | jq -r '.data[0].traceID')
oodle traces get "$TRACE_ID" --start -1h --end now -o json

# ❌ WRONG — `oodle traces get` without an id (it expects the id as a positional arg)
oodle traces get

# ❌ WRONG — `oodle traces get` without --start and --end (both are required)
oodle traces get "$TRACE_ID" -o json
```

### Time window flags

`--start` and `--end` are required for `list` and `get`. They accept the same formats as the rest of the CLI:

| Format | Example |
|--------|---------|
| Epoch seconds | `1731628800` |
| RFC 3339 | `2026-01-02T00:00:00Z` |
| `now` | `now` |
| Relative duration | `-1h`, `-30m`, `-7d` |

```bash
# ✅ CORRECT
oodle traces list --service api --start -30m --end now -o json

# ❌ WRONG — human strings are not parsed
oodle traces list --service api --start "30 minutes ago"
```

### TraceQL

`oodle traces traceql` has the alias `oodle traces tql`. Put the query in single quotes so the shell does not change `{`, `}`, `"`, or `!`.

```bash
# Find traces (filter only; a metrics query is rejected)
oodle traces traceql search '<filter>' [--start -1h] [--end now] [--limit 20]

# Time series from spans (needs a metrics function; a plain filter is rejected)
oodle traces traceql metrics '<filter> | <function>' [--start -1h] [--end now] [--step 5m] [--exemplars N] [-o graph|stats|json]

# Attribute names and values (reads only the last hour; no --start/--end)
oodle traces traceql tags [--scope resource|span|intrinsic|all] [-q '<filter>']
oodle traces traceql tag-values <tag> [-q '<filter>']
```

| Flag | Meaning |
|------|---------|
| `--start`, `--end` | `search` and `metrics`: default `-1h` and `now`. |
| `--limit` | `search`: maximum traces (default 20). |
| `--step` | `metrics`: `30s`, `5m`, `1h`, `1d`, or a number of seconds. Default `1m`, made larger to keep 300 points or fewer per series. |
| `--exemplars` | `metrics`: maximum exemplar traces. Shown only with `-o json` or `-o yaml`. |
| `-q`, `--query` | `tags`, `tag-values`: only look at spans that match this filter. |

Output: `search` prints one row per trace; `-o json` adds the matching spans and attributes. `metrics` prints one row per series (point count, last, min, max, average); `-o graph` draws a terminal chart, `-o stats` gives a summary, `-o json` gives all points.

#### Supported syntax

| Part | Supported |
|------|-----------|
| Spanset filter | `{ <condition> }` |
| Attribute scopes | `resource.<name>`, `span.<name>`, and unscoped `.<name>` (matches span or resource) |
| Intrinsics | `name`, `status`, `duration`, `kind` |
| Comparison | `=` `!=` `=~` `!~` `>` `>=` `<` `<=` |
| Combine in one spanset | `&&`, `||`, `!( ... )` |
| Union of spansets | `{ A } || { B }` |
| Attribute present / absent | `span.foo != nil`, `span.foo = nil` |
| Literals | strings `"x"`, numbers, durations `10s` `250ms`, `status = error` / `ok` / `unset`, `kind = server` / `client` / `internal` / `producer` / `consumer` |
| Metrics functions | `rate()`, `count_over_time()`, `avg_over_time(<field>)`, `min_over_time(<field>)`, `max_over_time(<field>)`, `sum_over_time(<field>)`, `histogram_over_time(<field>)`, `quantile_over_time(<field>, 0.5, 0.95, ...)` |
| Grouping | `by (<attribute>, ...)` after a metrics function |

#### Not supported yet

| Syntax | Use instead |
|--------|-------------|
| Scalar filters: `{ ... } \| count() > 2`, `\| avg(duration) > 1s` | `traceql metrics` with `count_over_time()` or `avg_over_time()`, and read the values |
| `{ A } && { B }` (two different spans of one trace) | One spanset `{ A && B }` when one span must match both |
| Structural operators `>>`, `<<`, `~` | Search for each span on its own, then compare trace ids |
| `parent.` and `link.` scopes | `span.` or `resource.` attributes |
| `rootName`, `traceDuration` | `name` and `duration` of a single span (for example, the root span by its name) |
| Bare existence: `{ span.foo }` | `{ span.foo != nil }` |

If a query fails with "not supported", rewrite it with the table above. Do not tell the user that TraceQL is not available.

#### Examples

```bash
# Errors in one service
oodle traces traceql search '{ resource.service.name = "checkout" && status = error }' --start -1h --limit 20

# GenAI tool calls over 10 s
oodle traces traceql search '{ span.gen_ai.tool.name != nil && duration > 10s }' --start -24h --limit 50

# Error rate by service
oodle traces traceql metrics '{ status = error } | rate() by (resource.service.name)' --start -6h --step 5m

# p50 and p95 latency per route, as a terminal chart
oodle traces traceql metrics '{ kind = server } | quantile_over_time(duration, 0.5, 0.95) by (span.http.route)' --start -1h -o graph

# Error rate with 5 example traces, for drill-down
oodle traces traceql metrics '{ status = error } | rate()' --start -1h --exemplars 5 -o json

# Attribute names and values
oodle traces traceql tags --scope span
oodle traces traceql tag-values span.http.route -q '{ resource.service.name = "checkout" }'
```

Monitors run PromQL, not TraceQL. To alert on traces, write the monitor query on the `oodle_trace_metrics` metric, or on the `oodle_genai_*` metrics for GenAI spans (see the oodle-monitors skill).

## Best Practices

### Always specify `--service` to scope the search

Trace volume can be massive; an unfiltered list is expensive and rate-limited.

```bash
# ✅ CORRECT
oodle traces list --service api --start -1h --end now -o json

# ❌ WRONG — full-tenant scan
oodle traces list --start -1h --end now -o json
```

### Use `--tags` to find failing traces, not client-side filtering

`--tags '{"span_status":"Error"}'` pushes the predicate to the server and returns matches faster.

```bash
# ✅ CORRECT
oodle traces list --service api --tags '{"span_status":"Error"}' --start -1h --end now -o json

# ❌ WRONG — fetches every trace then filters client-side
oodle traces list --service api --start -1h --end now -o json | jq '.data[] | select(.spans[].tags[] | .key=="error")'
```

### Use `--min-duration` to find slow traces, not client-side sort

```bash
# ✅ CORRECT — server-side filter, returns ~the right number of rows
oodle traces list --service api --min-duration 500ms --start -1h --end now -o json

# ❌ WRONG — pulls everything, sorts on the client
oodle traces list --service api --start -1h --end now -o json | jq '.data | sort_by(.spans[0].duration) | reverse'
```

### Keep time windows ≤ 24h for `list`; widen by re-querying, not by lowering `--start`

A 7-day list returns too many rows and risks timeouts. Page by hour.

```bash
# ✅ CORRECT — bounded window
oodle traces list --service api --tags '{"span_status":"Error"}' --start -1h --end now -o json

# ❌ WRONG — multi-day list
oodle traces list --service api --tags '{"span_status":"Error"}' --start -7d --end now -o json
```

## Failure Handling

| Error | Cause | Fix |
|-------|-------|-----|
| 401 Unauthorized | Invalid or missing API key | Run `oodle configure` or set `OODLE_API_KEY` |
| 404 Not Found | Trace ID does not exist or has aged out of retention | Re-list with a recent `--start` to find a current trace id |
| connection refused | Wrong `OODLE_DEPLOYMENT` URL | Check `OODLE_DEPLOYMENT` env var |
| Empty result | Wrong service name or time window outside retention | List recent services: `oodle traces label-values resource::service.name --start -1h --end now`; reduce the `--start` offset if it is past retention, or widen it if the window is too short (the CLI prints the range it read) |
| Request times out | Time window too large or no service filter | Narrow `--start`/`--end`, add `--service`, add `--min-duration` or `--tags` |
| 429 Too Many Requests | Concurrent trace fetches | Add `--retries 3`; throttle to <5 requests / second |
| `required flag(s) "end", "start" not set` | `list` and `get` need both flags | Add `--start -1h --end now` |
| 400 on `--min-duration` | Duration has no unit | Use `500ms`, `2s` |
| TraceQL `... is not supported` | Syntax from "Not supported yet" | Rewrite with the replacements in that table |
| `search` rejects the query | It has a metrics function (`\| rate()` …) | Use `oodle traces traceql metrics` |
| `metrics` rejects the query | It has no metrics function | Add one, or use `oodle traces traceql search` |

## References

- [Oodle CLI repo](https://github.com/oodle-ai/oodle-cli)
- [Oodle docs](https://docs.oodle.ai)
- [OpenTelemetry trace concepts](https://opentelemetry.io/docs/concepts/signals/traces/)
- [TraceQL](https://grafana.com/docs/tempo/latest/traceql/)

Mirrored from the author's public source. Install counts from the open skills registry.

The systems behind these skills get built for partners every week.

Partner with us