---
title: "Telemetry Inspect"
description: "Inspects the OrchestKit telemetry pipeline for the current project, lists all known telemetry files with write counts, sizes, schema status, growth trend, and orphan detection. Use when verifying the observability pipeline is healthy, debugging a missing writer, or auditing which files have schema locks vs. which are drift-vulnerable. Read-only, never modifies telemetry files."
canonical: "https://orchestkit.yonyon.ai/docs/reference/skills/telemetry-inspect"
---

# Telemetry Inspect

Inspects the OrchestKit telemetry pipeline for the current project, lists all known telemetry files with write counts, sizes, schema status, growth trend, and orphan detection. Use when verifying the observability pipeline is healthy, debugging a missing writer, or auditing which files have schema locks vs. which are drift-vulnerable. Read-only, never modifies telemetry files.

<span className="badge badge-blue">Command</span> <span className="badge badge-green">low</span>

```bash title="Invoke"
/ork:telemetry-inspect
```

<ContextualSkillSidebar slug="telemetry-inspect" />

> **Telemetry Inspect** Inspects the OrchestKit telemetry pipeline for the current project, lists all known telemetry files with write counts, sizes, schema status, growth trend, and orphan detection. Use when verifying the observability pipeline is healthy, debugging a missing writer, or auditing which files have schema locks vs. which are drift-vulnerable. Read-only, never modifies telemetry files.


# telemetry-inspect

One-shot health check for OrchestKit's telemetry pipeline. Reports writer activity, file sizes, schema lock coverage, orphan files, and growth warnings. Use when verifying the pipeline is flowing correctly or debugging a missing writer.

## When to use

- Before or after a risky hook refactor, to prove telemetry still writes as expected
- Weekly health check on a long-running project
- When `analytics` output looks suspicious, inspect the underlying data first
- When adding a new telemetry file and wanting to confirm it's picked up
- Auditing which files are schema-locked vs. drift-vulnerable

## What it checks

1. **Writer activity**: for each registered telemetry file, recent write count (from mtime scan) and last-write delta
2. **File health**: size (warn at 256 KB, critical at 1 MB), line count, mtime
3. **Schema lock status**: which files have validators in `lib/telemetry-schemas.ts`
4. **Orphan detection**: files on disk under `.claude/{telemetry,logs,state,feedback}/` that aren't in the registry (possible stale writer or new file needing schema), plus inventory rows whose writer hook no longer exists in `src/hooks/src/` (dead writer → orphan)
5. **Growth trend**: bytes per hour since session start (fire alert if > 100 KB/hr)
6. **Coordination layer (M168)**: live counts from `sessions.db` (running sessions, held locks, pending worktree links, skill invocations) plus write throughput from `coordination-metrics.jsonl`
7. **Runtime fired-census**: which hooks actually *fired*, from the per-invocation records `run-hook.mjs` writes to `~/.claude/analytics/hook-timing.jsonl`. Checks 1-4 answer "is the file being written?"; this answers "is the *writer* running at all?", the upstream question. A hook can be wired and reachable (the closure gate proves that statically) and still never fire: #2886 shipped exactly that with 12 green tests.

## Usage

```bash
telemetry-inspect
telemetry-inspect --session sess-abc123
telemetry-inspect --json
```

Default mode: terminal-friendly ASCII report. `--json` emits a structured result suitable for piping into another tool or uploading.

## Output shape (ASCII mode)

```
Telemetry Health — 2026-07-09 11:50
────────────────────────────────────

Schema-locked files (7)
  .claude/telemetry/pre-compact-decisions.jsonl  ◆ 3 lines  1.1 KB  ✓ healthy
  .claude/telemetry/image-responses.jsonl        ◆ 0 lines  —       ✗ no writes
  .claude/logs/decisions.jsonl                   ◆ 0 lines  —       ✗ no writes
  .claude/logs/subagent-spawns.jsonl             ◆ 6 lines  3 KB    ✓ healthy
  .claude/state/edit-history.jsonl               ◆ 94 lines 412 KB  ⚠ rotate
  .claude/state/ork-metrics-*.json               ◆ (N/A)    2.1 KB  ✓ healthy
  .claude/logs/skill-channels.jsonl              ◆ 12 lines 4 KB    ✓ healthy

Unlocked telemetry files (14)
  .claude/feedback/changelog-decisions.json      ○ 4 KB    ✗ no schema
  .claude/feedback/learned-patterns.json         ○ 8 KB    ✗ no schema
  (...14 more...)

Orphan files (1)
  .claude/feedback/skill-usage.json  — delisted, writer unwired since #959

Runtime fired-census (7d window)
  ALIVE  59   IDLE 3   NEVER 83   UNOBSERVABLE 54
  tree 8.75.0 · installed 8.73.0/8.74.0  <-- SKEW: NEVER is not a verdict
  never-fired writers (the ones that matter here):
    posttool/dirty-file-tracker   [PostToolUse]   0 fires, ever

Summary
  Pipeline health:  GREEN  (21/21 expected writers active)
  Schema coverage:  7/21 (33%)
  Largest file:     edit-history.jsonl (412 KB)
  Hotspot:          edit-history.jsonl  +40 KB/hr
```

## Implementation plan (for an agent/LLM running this skill)

1. **List known files**: read `lib/telemetry-schemas.ts`'s `SCHEMA_LOCKED` inventory for the 7 locked paths. Extend with the unlocked paths listed in the skill-local `references/telemetry-inventory.md`.
2. **Cross-check inventory writers against the registry closure**: never trust the inventory blindly:
   - Run `node src/hooks/scripts/validate-registry.mjs` (from the OrchestKit repo root; skip gracefully if not in the OrchestKit repo) and confirm it passes.
   - For every writer named in the inventory (e.g. `posttool/metrics-bridge`), verify the source file exists: `test -f src/hooks/src/<writer>.ts`.
   - Any inventory writer that is NOT a live `src/hooks/src/` file is **ORPHANED, report 🔴**, never "expected-but-empty". Its file(s) on disk are orphans regardless of freshness, and the inventory row is stale (flag it for removal).
3. **For each file**:
   - Use `Glob` to resolve `.claude/state/ork-metrics-*.json` pattern → may be multiple
   - Use `Read` with `limit: 10` to see shape and `Bash wc -l` for line count
   - Use `Bash stat` for mtime + size
4. **Classify health**:
   - size > 1 MB → critical
   - size > 256 KB → warn
   - mtime > 7 days → "no recent writes"
   - line count 0 → "no writes"
5. **Orphan scan**: `Bash find .claude/{telemetry,logs,state,feedback} -type f` cross-check against registered paths. Any on-disk files not in inventory → orphan. Merge in dead-writer orphans from step 2.
6. **Runtime fired-census**: steps 1-5 ask "is the file being written?". This asks the upstream question: "does the writer hook run at all?"
   - Run `node src/hooks/scripts/fired-census.mjs --json` (OrchestKit repo only; skip gracefully elsewhere, it exits 0 and prints an absent-census notice if `~/.claude/analytics/hook-timing.jsonl` doesn't exist yet).
   - Report the class counts (`totals`), and the `versions.skewed` line **verbatim when true**, the census is written by the INSTALLED plugin across ALL projects, while the closure comes from this tree, so `NEVER` on a hook newer than the installed build is expected, not a finding.
   - Cross-reference: for every inventory writer from step 2 that is a live file, look up its census row. A writer that is `ALIVE` but whose file has 0 lines is a **real defect** (it runs and produces nothing). A writer that is `NEVER` is the #2886 class, surface it, but as advisory.
   - **Never** call a hook dead from `NEVER` alone. `UNOBSERVABLE` rows (dispatcher children) *cannot* appear by construction, and rare events (`WorktreeRemove`, `PreCompact`) look identical to a broken guard from here. Read the guard before concluding.
7. **Render report**: ASCII table by default, JSON if `--json` argument passed.

Core logic is deterministic + read-only. Do NOT write to any telemetry file, this skill is an observer.

## Coordination layer (M168 #1915)

The SQLite coordination layer lives **outside `.claude/`**, at `~/.local/state/orchestkit/`:

| Source | What it tells you |
|--------|-------------------|
| `sessions.db` | live session / lock / worktree state (SQLite) |
| `events.jsonl` | coordination event stream (goal_converged, chain_stale, …) |
| `coordination-metrics.jsonl` | `sessions.db` write throughput counters (#1915) |

**Live counts**: the DB file is a standard SQLite database; read it with `sqlite3` (read-only SELECTs only):

```bash
DB="$HOME/.local/state/orchestkit/sessions.db"
[ -f "$DB" ] || echo "coordination layer idle (no multi-session activity yet)"
sqlite3 "$DB" "SELECT COUNT(*) FROM sessions WHERE status='running'"                  # live sessions
sqlite3 "$DB" "SELECT COUNT(*) FROM skill_invocation"                                 # skill invocations
```

**Write throughput**: `coordination-metrics.jsonl` is append-only `{ts, metric, count}` lines emitted async by `lib/metrics-emitter.ts` on every `sessions.db` write. Event rate ≈ recent `sessions_db_write` lines:

```bash
M="$HOME/.local/state/orchestkit/coordination-metrics.jsonl"
[ -f "$M" ] && tail -200 "$M" | grep -c '"sessions_db_write"'
```

Degrade gracefully: if `sqlite3` is absent or the DB / metrics file doesn't exist, report **"coordination layer idle"**, never error. Like the rest of this skill, these are **read-only** observations.

## Upstream OTel metric notes

When inspecting Claude Code's own OTel metrics (downstream of this skill, `claude_code.*` in your collector):

- **CC 2.1.129+**: `claude_code.pull_request.count` now also counts PRs/MRs filed via MCP tools (e.g., GitHub MCP `create_pull_request`), not just shell commands run through the Bash tool. Dashboards built before 2.1.129 will see a step-function increase at the cutover, annotate, don't alert. See `references/../monitoring-observability/references/metrics-collection.md` for the join pattern that distinguishes MCP- from shell-filed PRs.
- **CC 2.1.161+**: `OTEL_RESOURCE_ATTRIBUTES` values are now attached as labels on all metric datapoints, enabling dimensional slicing (team, repo, environment). Existing dashboards keep working; new dashboards should use label selectors to segment usage.
- **CC 2.1.145+**: `claude_code.tool` OTEL spans carry `agent_id` + `parent_agent_id`, and background subagent spans nest under the dispatching Agent tool span. Build the trace tree by querying on `parent_agent_id`, enables per-skill fan-out timing and cost attribution for multi-agent skills (`brainstorm`, `explore`, `implement`); no schema change needed.
- **CC 2.1.174+**: `/usage` exposes CC-native per-component attribution, cache misses, long context, subagents, and per-skill/agent/plugin/MCP cost breakdowns over 24h/7d (surfaced first in the VSCode Account & usage dialog). Treat it as a cross-check source in the health report: if ork telemetry shows a skill/agent active but CC attribution shows zero usage for it (or vice versa), flag the divergence as a possible missing writer or stale install rather than trusting either side alone.
- **CC 2.1.202+**: telemetry from workflow-spawned agents (a `/workflows` run) carries `workflow.run_id` + `workflow.name` attributes, so a whole workflow run is reconstructable from OTel, group all agent events on `workflow.run_id` to rebuild one run's fan-out, and slice by `workflow.name`. See `../analytics/references/otel-fields.md` ("From 2.1.202") for the field table; filter `select(.["workflow.run_id"] != null)` first, since ordinary session events lack these attributes.

## Related

- `lib/telemetry-schemas.ts`: source of truth for schema-locked paths
- `analytics`: aggregates data across sessions (different use case)
- M121 "Observability Consolidation" milestone


---

## References (1)

### Telemetry Inventory

# OrchestKit Telemetry File Inventory

Single source of truth listing every file that OrchestKit hooks write to. The `telemetry-inspect` skill reads this inventory to distinguish "expected" files from orphans.

Last updated: 2026-07-15 (dead-hook triage, see changelog at bottom)

## Path routing helpers

Writers do not hard-code directories, they go through four helpers. Know these to attribute any file:

| Helper | Source | Target directory |
|--------|--------|------------------|
| `writeTelemetryEvent()` | `lib/telemetry-jsonl.ts` | `.claude/telemetry/` (or plugin-data/telemetry on CC ≥ 2.1.78) |
| `appendEventLog(file, entry)` | `lib/event-logger.ts:17` | `<project>/.claude/logs/` |
| `appendAnalytics(file, entry)` | `lib/analytics.ts:55` | `getAnalyticsDir()` = `~/.claude/analytics/`, HOME-relative, not project-relative (NOT `.claude/logs/`, NOT `.claude/memory/`) |
| `logHook()` / `logPermissionFeedback()` | `lib/log.ts:51,75` | `getLogDir()` = `.claude/logs/` |

Note: `appendAnalytics` writes its own filename set (`task-usage.jsonl`, `team-activity.jsonl`, `agent-usage.jsonl`, `subagent-quality.jsonl`, `session-summary.jsonl`) into the analytics dir, distinct from the `.claude/logs/` files below, and dual-writes to the yonatan-hq platform sink via `postAnalyticsToSink`.

## Schema-locked files (7): validators in `lib/telemetry-schemas.ts` (`SCHEMA_LOCKED`)

| Path | Writer (hook) | Schema validator |
|------|--------------|------------------|
| `.claude/telemetry/image-responses.jsonl` | `posttool/context-crossing-warn` | `isValidImageResponseEntry` |
| `.claude/telemetry/pre-compact-decisions.jsonl` | `lifecycle/pre-compact-task-done-prompt` | `isValidPreCompactDecisionEntry` |
| `.claude/logs/decisions.jsonl` | **none, producer never built** (schema shipped in M121 `d5ea63562` without a writer; `lifecycle/pre-compact-saver` and `stop/handoff-writer` are READERS that degrade gracefully on absence. A third reader, `prompt/context-exhaustion-warner`, was deleted in #3427, its own data source had no writer either) | `isValidDecisionLogEntry` |
| `.claude/logs/subagent-spawns.jsonl` | `pretool/task/spawn-intent-logger`, `subagent-start/subagent-validator` | `isValidSubagentSpawnEntry` |
| `.claude/state/edit-history.jsonl` | `posttool/write/edit-history-tracker` | `isValidEditHistoryEntry` |
| `.claude/state/ork-metrics-*.json` | `posttool/metrics-bridge` | `isValidOrkMetricsSnapshot` |
| `.claude/logs/skill-channels.jsonl` | `pretool/skill/skill-tracker` (main channel, dispatch restored 2026-07-09 after the #959 drop), `subagent-stop/skill-channel-tracker` (subagent channel) | `isValidSkillChannelEntry` |

> `image-responses.jsonl` and `skill-channels.jsonl` are schema-locked but may be absent on disk for a given project, they are written only when their trigger fires (image bytes in a tool result; a Skill invocation). Absence is not an orphan.
>
> **Known gap:** `decisions.jsonl` is schema-locked with NO producer, the M121 observability consolidation shipped its schema, lock, and three readers, but the writer was never built. Absence is guaranteed, not conditional. Follow-up: build the producer or drop the lock.
>
> **Delisted 2026-07-09:** `.claude/feedback/skill-usage.json` was removed from `SCHEMA_LOCKED`, its writer has been unwired since 2026-03-06 (see Deprecated section). The validator / canonical / interface are retained for back-compat shape-checking of the frozen file, but the path is no longer schema-locked, so `telemetry-inspect` now correctly flags it as an orphan rather than an expected writer.

## Live unlocked writers: no schema validator yet

### `.claude/telemetry/`

| Path | Writer |
|------|--------|
| `.claude/telemetry/events.jsonl` | `lib/telemetry-jsonl` (`writeTelemetryEvent`, via `lib/jsonl-sink`) |

### `.claude/logs/`

| Path | Writer |
|------|--------|
| `.claude/logs/task-completions.jsonl` | `task-completed/completion-tracker` (`appendEventLog`) |
| `.claude/logs/task-creations.jsonl` | `task-created/creation-tracker` (`appendEventLog`) |
| `.claude/logs/task-nudge-outcomes.jsonl` | `stop/task-completion-check` (`appendEventLog`, ≤2 lines/session; reader: `scripts/task-compliance.mjs`) |
| `.claude/logs/memory-consult.jsonl` | `pretool/mcp/memory-validator` (`appendEventLog`) |
| `.claude/logs/teammate-activity.jsonl` | `posttool/task/team-member-start`, `teammate-idle/progress-reporter` (`appendEventLog`) |
| `.claude/logs/worktree-events.jsonl` | `worktree/exit-finalizer` (`appendEventLog`) |
| `.claude/logs/config-changes.jsonl` | `config-change/settings-reload` |
| `.claude/logs/config-audit.jsonl` | `posttool/config-change/security-auditor` |
| `.claude/logs/context7-telemetry.log` | `pretool/mcp/context7-tracker` |
| `.claude/logs/hooks.log` | `lib/log.ts` (`logHook`, all hooks) |
| `.claude/logs/permission-feedback.log` | `lib/log.ts` (`logPermissionFeedback`) |
| `.claude/logs/permission-denials.jsonl` | `permission-denied/denial-logger`, `permission-denied/denial-notification` |
| `.claude/logs/agent-state.json` | `subagent-start/context-gate` |

### `.claude/state/`

| Path | Writer |
|------|--------|
| `.claude/state/goal-history.jsonl` | `lifecycle/goal-budget-guard`, `prompt/goal-tracker` |
| `.claude/state/goal-budget-tripped.json` | `lifecycle/goal-budget-guard`, `prompt/goal-tracker` |
| `.claude/state/last-test-run.json` | `pretool/bash/pre-commit-test-gate` |
| `.claude/state/plugins-snapshot.json` | `lifecycle/plugins-drift-snapshot`, `posttool/check-plugins-drift` |
| `.claude/state/dev-stack.json` | `lib/dev-stack-state` |
| `.claude/state/expect-auto-fires.json` | `posttool/ui-change-detector` |
| `.claude/state/expect-snapshots/` | `posttool/expect/snapshot-recorder` |
| `.claude/state/worktree-advisory-*.md` | `lib/worktree-advisory` (consumed by `prompt/worktree-advisory-consumer`) |
| `.claude/state/session-*-token-accum.json` | `lib/session-token-accum` (called from `lifecycle/pre-compact-task-done-prompt`, `posttool/context-crossing-warn`) |

### `.claude/feedback/`

| Path | Writer |
|------|--------|
| `.claude/feedback/learned-patterns.json` | `lifecycle/pattern-sync-push`, `lifecycle/pattern-sync-pull`, `permission/learning-tracker` |
| `.claude/feedback/consent-log.json` | `lifecycle/analytics-consent-check` |
| `.claude/feedback/dependency-check-cache.json` | `lifecycle/dependency-version-check` |
| `.claude/feedback/changelog-decisions.json` | `lib/decision-history` |
| `.claude/feedback/instruction-drift-cache.json` | `instructions-loaded/drift-detection` |

## Deprecated / orphaned files (writer removed or unwired: flag as orphans, do not re-create)

These files exist on disk from historical runs but have no reachable writer at HEAD. All froze on the dates shown.

| Path | Last write | Root cause |
|------|-----------|-----------|
| `.claude/feedback/skill-usage.json` | 2026-03-06 | Writer `posttool/skill/skill-usage-optimizer` unwired by commit `e3e99b2ff` (#959, "PostToolUse: 17→3 sub-hooks"). Function + dispatch-map entry survive but no `hooks.json` entry invokes the key → unreachable. Superseded by M168 (#2015) `skill_invocation` SQLite table. **Delisted from SCHEMA_LOCKED 2026-07-09.** |
| `.claude/logs/skill-usage.log` | ~~2026-03-06~~ **revived 2026-07-09** | Writer `pretool/skill/skill-tracker` was unwired in the same #959 prune, but unlike the optimizer it is NOT superseded, it is the sole feeder of the M168 `skill_invocation` SQLite table (`recordInvocation`) and of `skill-channels.jsonl` main (#2154). Dispatch restored in hooks.json; live again. |
| `.claude/logs/skill-analytics.jsonl` | 2026-03-03 | No current writer; last source touch was M168 (#2015), which moved skill usage to the SQLite `skill_invocation` table. |
| `.claude/logs/memory-metrics.jsonl` | 2026-03-03 | Was referenced in `lifecycle/unified-dispatcher`, which was itself deleted in the 2026-07-15 dead-hook triage. Pure orphan now. |
| `.claude/logs/agent-patterns.jsonl` | 2026-02-12 | No writer source file has ever existed, pure disk orphan. |
| `.claude/logs/background-hooks.log` | 2026-02-08 | No writer source file has ever existed, pure disk orphan. |
| `.claude/feedback/satisfaction.json` / `satisfaction.log` | 2026-01-23 / 2026-03-06 | Writer source removed in #959. |
| `.claude/feedback/workflow-patterns.json` | 2026-03-06 | No writer in source, pure orphan. |
| `.claude/feedback/calibration-data.json` | 2026-03-06 | No writer in source, pure orphan. |
| `.claude/feedback/evolution-registry.json` | 2026-01-22 | No writer in source, pure orphan. |

## Directory health budgets

| Directory | Target max total size | Action if exceeded |
|-----------|----------------------|-------------------|
| `.claude/telemetry/` | 5 MB | rotate oldest JSONL lines (`rotateTelemetryIfNeeded`) |
| `.claude/logs/` | 10 MB | archive `*.old.*` to `.claude/logs/archive/` |
| `.claude/state/` | 2 MB | truncate; state is ephemeral |
| `.claude/feedback/` | 5 MB | consolidate patterns |

## How to add a new telemetry file

1. Add the writer (hook) that produces it, route through `appendEventLog` (`.claude/logs/`) or `writeTelemetryEvent` (`.claude/telemetry/`).
2. Add an entry to the appropriate section above.
3. If the file has a stable shape, add a validator to `lib/telemetry-schemas.ts`, append to the `SCHEMA_LOCKED` array (currently 7), and promote it to the Schema-locked table.
4. Add tests to `__tests__/lib/telemetry-schemas.test.ts` (including the `SCHEMA_LOCKED.length` count assertion).

## How to retire a telemetry file

1. Remove or unwire the writer.
2. Remove its `SCHEMA_LOCKED` entry (and update the length assertion in the test) so `telemetry-inspect` reports the leftover file as an orphan instead of an expected writer.
3. Move it to the Deprecated section above with the removal commit + date.

## Changelog

**2026-07-15 dead-hook triage**: the following rows were removed because their writer hook files no longer exist under `src/hooks/src/` at HEAD. Any of these files still on disk are orphans (report 🔴):

- `.claude/logs/audit.log`: writer `posttool/audit-logger` deleted. The row's other listed "writers" were mislistings: `lifecycle/session-cleanup` only prunes `audit.log.old*` archives, and `agent/security-command-audit` writes `security-audit.log` (a different, uninventoried file).
- `.claude/feedback/code-style-profile.json`: writer `posttool/write/code-style-learner` deleted.
- `.claude/feedback/naming-conventions.json`: writer `posttool/write/naming-convention-learner` deleted.
- `.claude/feedback/tool-preferences.json`: writer `posttool/tool-preference-learner` deleted.
- `.claude/feedback/patterns-queue.json`: writer `posttool/bash/pattern-extractor` deleted.

Also verified deleted in the same triage (never had live rows here, or were already in the Deprecated section): `posttool/skill/skill-usage-optimizer`, `posttool/dirty-file-tracker`, `stop/workflow-preference-learner`, `posttool/skill-edit-tracker`, `subagent-stop/context-publisher`, `posttool/realtime-sync`, `posttool/session-metrics`, `lifecycle/unified-dispatcher`.
