Enable Project Telemetry
Telemetry capture and log export are gated on the canonical repository ID in the local opt-in registry. Installing this plugin at user, project, or local scope does not itself choose every repository. Disabling this repository stops future capture and future export of still-queued log rows while local history is retained. Collector traces and metrics are not covered by this gate. This skill asks for the shared service choice, then writes the current repository's opt-in wiring.
Claude Code reads OTEL_RESOURCE_ATTRIBUTES from the settings of the directory a session started in and does
not walk up to the repository root. That is why the enable command writes the env into the repository root
and into each subdirectory that has hosted Claude Code sessions, all carrying the repository's
project.id. Hook events from linked worktrees resolve to the enclosing main repository identity, but native
Claude Code metrics from a linked-worktree-started session need wiring in that exact start directory. Do not
promise linked-worktree native coverage from this repository opt-in alone.
Read setup-reference.md before the first enable on a machine. It owns the separate shared-service consent question, the
setup-observability.js commands that install the pinned Collector and choose a sink, dashboard, and ports,
and the deletion rules. Do the shared service setup pass first, then the per-project wiring below.
Enable
Gateway wiring is per-scope. If the gateway is unwired for the current project, invoke /model-gateway:model-gateway and use its env --write-project command to wire this project. Use env --write-user only when machine-wide wiring is wanted. Do not invoke a bare codex-gateway shell command, since the installed plugin command is not on PATH.
Confirm the user wants the shared local observer and Collector, then separately confirm that the current repository should opt in. Say the repository opt-in writes only this repository's
.claude/settings.local.jsonfiles and adds it to the local project registry. The shared service can send local metadata through the loopback observer and Collector to local Grafana, or to a remote sink only when the user chooses one. Bare setup is SQLite-only with no dashboard;--dashboardexplicitly requests the Docker-backed dashboard. Explain that API-equivalent cost estimates are not subscription charges, and models without a published API price remain visible without an invented USD total. The intended telemetry schema excludes prompt or response text, code or file contents, tool inputs or results, raw request bodies, credentials, and environment values.Telemetry capture and log export are gated on the canonical repository ID in the local opt-in registry. Installing this plugin at user, project, or local scope does not itself choose every repository. Disabling this repository stops future capture and future export of still-queued log rows while local history is retained. Collector traces and metrics are not covered by this gate.
Run it from anywhere inside the repository; it resolves the repository root itself:
node "${CLAUDE_PLUGIN_ROOT}/bin/project-telemetry.js" --project "<absolute-current-project-dir>"This creates or merges the
envobject without dropping unrelated keys. It adds the Claude Code telemetry settings, includingOTEL_RESOURCE_ATTRIBUTESwith the SHA-256 repository ID asproject.id, the sanitized repository basename asproject.name, andservice.name=claude-code. The registry stores the same sanitized name plus the SHA-256 repository ID used by this plugin's hooks. Re-running enable rewrites this repository's env with those values; it never rewrites any other project's settings. Session-hosting subdirectories are found by encoding each real subdirectory the way Claude Code encodes~/.claude/projects/names and keeping the ones that exist there;.claude/worktrees,.git,node_modules, dot-directories, and nested repositories are skipped.Report every directory the command printed, and tell the user settings environment changes apply only to new Claude Code sessions. Restart every already-running session in those directories before creating activity or running verification.
/reload-pluginsalone does not apply the new environment. Hook-based linked-worktree attribution can still resolve to the main repository, but native metrics require the exact session-start directory to be wired.After that new session creates activity, verify honestly:
node "${CLAUDE_PLUGIN_ROOT}/bin/verify-project-telemetry.js" --project "<absolute-current-project-dir>"foundreports that Prometheus returned aclaude_code_token_usage_tokens_totalmetric tagged with this project; it does not report observer health. Read the separateobserver=healthyorobserver=unavailablefield too, because output can befoundwithobserver=unavailable.not-foundmeans the command did not see the metric yet, or no dashboard is configured. Report both fields as returned, and never infer observer health fromfoundalone. If the dashboard is unavailable, keep the observer and SQLite ingestion running; diagnose and report the observer, collector, downstream sink, and dashboard as separate planes.When
not-foundpersists after a restart and real activity, add--auditto the same command. It lists the repository's session directories with their wiring state, names any opted-in project sending observer events with noclaude_code_*samples, and prints the command that fixes it.
Storage pressure
The observer reserves 128 MiB below its 4 GiB database limit. It prunes expired observations first, then oldest whole days inside the 30-day window only when pressure remains, and records the exact windows and row counts in /health. A health failure of storage_headroom_unrecoverable means no removable data restored that reserve. Explain that committed ingestion still receives its normal acknowledgement, then diagnose disk and retention pressure from the health response. Do not tell the user to run VACUUM: the managed path compacts reusable pages when it can, and the manual prune command checks free space before a full vacuum.
Exhausted outbox recovery
When /health or the outbox view reports exhausted rows, show the user the pre-action pending_count and exhausted_count plus the current health result. Ask for approval before sending POST /v1/outbox/requeue to the local observer. The endpoint resets all exhausted rows in the shared local outbox, so do not offer or imply a project-scoped requeue. After the approved request, read the counts and health again, report the post-action values, and let the normal drainer retry delivery. A requeue does not prove delivery succeeded.
Recover a generated dashboard
If the generated dashboard is stale, reset it with the setup command's --reset-dashboards action. The
reset removes generated definitions and records a reset boundary; it does not disable telemetry or delete
local history. Create fresh activity, then run setup or let SessionStart reprovision the dashboards. Fully
reload the Grafana browser tab after reprovisioning. Grafana's Refresh reruns queries already loaded in the
page, so it does not replace stale dashboard definitions. Verify the project after the new activity arrives,
and report found or not-found as returned.
Disable
node "${CLAUDE_PLUGIN_ROOT}/bin/project-telemetry.js" --project "<absolute-current-project-dir>" --disable
It unwires the same set enable wired, from wherever in the repository it is run, using each directory's own state file. It restores only env values this flow replaced, preserves later user edits and unrelated settings, and removes the repository from the machine-local opted-in registry. It leaves the shared observer, Collector, and historical local data alone because another opted-in project may still use them. Restart Claude Code in each listed directory for the removed env block to take effect.
Manual scratch-project check
Use this only when validating the flow itself. Set a temporary home and project directory, invoke the enable
command from that directory, inspect .claude/settings.local.json for the telemetry block, then run the verify
command before any new session activity. Its expected honest result is not-found until a restarted Claude Code
session emits telemetry. A scratch directory with no .git above it is its own project, which is what makes it
a scratch project rather than part of the surrounding repository.