Skip to content

Repository files navigation

claude-otel

Version: 0.2.1 (see VERSION, CHANGELOG.md)

Claude Code OTEL telemetry collection + Panther SIEM pipeline.

Start here: docs/getting-started.md — full checklist (S3, KMS, Vault, device cert, privacy profile).


What this is

claude-otel provides:

  1. A local OpenTelemetry collector (otelcol-contrib) + Jaeger all-in-one running as user-scope services/agents. It receives OTLP from Claude Code and writes append-only JSONL for logs, metrics, and traces plus forwards traces to Jaeger for UI.
  2. Configuration for Claude Code (via generated .claude/settings.json) to emit rich telemetry (user prompts, tool calls, costs, tokens, MCP, api requests, etc.) with cotel.* resource attributes for attribution.
  3. A session-oriented export pipeline that turns raw OTEL files into one gzip-compressed JSONL per completed session and uploads it to your configured S3 bucket for ingestion by Panther SIEM (macOS only).

Architecture

Diagrams (rendered on GitHub):

docs/claude-otel-0.2.0.md — collection stack, Panther sync, state machine, trust chain

Mermaid source (for editors / mmdc): docs/claude-otel-0.2.0.mmd

Claude Code → OTLP :4317 → otelcol-contrib → logs.jsonl / metrics.jsonl / traces.jsonl
                                              └─ OTLP → Jaeger :16686

sync.sh (macOS, every 4h)
  → export eligible sessions (idle ≥48h)
  → per file: vault CLI STS creds (via vault-connector) → gzip → cotel-s3-upload.py → S3
  → confirm upload → prune confirmed lines from local logs

Platform support

Platform Collection Panther sync
macOS arm64 / x86_64 LaunchAgents under /Library/LaunchAgents/ (.pkg payload) Yes — com.customer.claude-otel-sync
Linux systemd user units under ~/.config/systemd/user/ No (device CN + LaunchAgent are mac-specific)

Install prefix (macOS): /Library/Application Support/claude-otel/.
Per-user data: ~/Library/Application Support/claude-otel/ (macOS) or ~/.local/share/claude-otel/ (Linux).

Full design spec: design-0.2.0.md


Prerequisites

All platforms

Requirement Notes
Claude Code CLI Installed and authenticated
Python 3.9+ Export / upload scripts (python3 on PATH)
Network Install / pkg build downloads otelcol-contrib and Jaeger release binaries

macOS — collection + Panther sync

Requirement Severity Notes
macOS arm64 or x86_64 Required Package is single-arch per build
python3, gzip, security, openssl Hard fail (preflight) Standard Xcode CLI / base OS tools
HashiCorp vault CLI Hard fail (unless lab flag) brew install vault — used through vault-connector, not raw curl
vault-connector Hard fail (unless lab flag) Secrets proxy: vault-connector secrets start
Org device certificate in System keychain Soft warn Subject must match COTEL_CERT_SUFFIX (default @example.com)
Free ports 4317 and 16686 Soft warn Collector OTLP + Jaeger UI
Xcode CLI tools (pkgbuild) Build host only Needed to build the .pkg

Preflight (recommended before install):

make preflight-macos
# collection-only lab (skip vault hard-fail):
bash install/macos/preflight.sh --allow-missing-vault

AWS / SIEM (sync only)

Requirement Notes
S3 bucket (+ optional KMS) Terraform template: infra/telemetry-bucket.tf
Vault AWS secrets engine role Role that issues short-lived writer IAM creds
Panther (or other SIEM) S3 log source pointed at the bucket

Credentials are never stored in cotel.env. Sync fetches STS via vault read <VAULT_AWS_MOUNT>/creds/<VAULT_AWS_ROLE> through the local vault-connector proxy.

Linux — collection only

  • systemd user session available
  • No Vault / device cert required (no Panther sync on Linux)

Quick start

1. Deployment config (macOS sync)

cp config/cotel.env.example config/cotel.env
$EDITOR config/cotel.env

config/cotel.env is gitignored. Set at least:

Variable Example default Purpose
BUCKET claude-otel-telemetry Destination S3 bucket
KMS_KEY_ID alias/claude-otel-telemetry SSE-KMS key; use AES256 / empty / none for SSE-S3
AWS_REGION us-east-1 S3 region
VAULT_AWS_MOUNT aws Vault AWS secrets engine mount path
VAULT_AWS_ROLE claude-otel-telemetry-writer Role under <mount>/creds/<role>
COTEL_CERT_SUFFIX @example.com security find-certificate -c filter for device CN
MIN_IDLE_HOURS 48 Session idle hours before export (use 0 only in labs)

2. Install

# Linux (collection only)
make install-linux
make start
make verify

# macOS (preflight + build .pkg + silent installer — no GUI click)
make install-macos
# optional:
#   make install-macos INSTALL_MACOS_FLAGS=--open                 # Installer.app UI
#   make install-macos INSTALL_MACOS_FLAGS=--allow-missing-vault  # collection-only lab
#   make pkg-macos                                                # build only
#   make bundle-macos                                             # .pkg + install/uninstall/preflight tarball
#   make uninstall-macos                                          # full wipe

Silent install runs:

preflight → pkgbuild → sudo installer -pkg dist/claude-otel-*.pkg -target /

LaunchAgent plists are package payload (written by installd) — not sed/bash on the endpoint (SentinelOne-safe). See docs/macos-pkg-install.md.

After a non-baked install, confirm:

$EDITOR "/Library/Application Support/claude-otel/config/cotel.env"
"/Library/Application Support/claude-otel/bin/install-claude-settings"

3. Test export (no Vault / S3)

python3 scripts/cotel-panther-export.py \
  --logs examples/samples/logs-sample.jsonl \
  --metrics examples/samples/metrics-sample.jsonl \
  --device-cn "test-device.example.com" \
  --min-idle-hours 0 \
  --state /tmp/fresh-export-state.json \
  --out-dir /tmp/test-export

Org deployment bake (fleet / MDM)

For organization-wide rollout, bake production cotel.env into the package so endpoints get correct bucket / Vault / cert settings on first install with no manual edit. Works with Kandji Custom Apps, Jamf, or any MDM that runs installer -pkg �� -target /.

Full notes: docs/kandji-mdm-deployment.md · docs/macos-pkg-install.md

What bake does

Stage Behavior
Build host If config/cotel.env exists (or COTEL_ENV_SOURCE=…), validate required keys and ship as config/cotel.env.default inside the .pkg
First install postinstall copies cotel.env.default → cotel.env when cotel.env is missing
Upgrade Existing on-disk cotel.env is left alone (ops edits preserved)
Zero-touch / DEP No console user at install is OK — launchd loads agents at first login; run-collector self-heals Claude OTEL settings

Validated keys: BUCKET, VAULT_AWS_MOUNT, VAULT_AWS_ROLE, COTEL_CERT_SUFFIX, AWS_REGION, MIN_IDLE_HOURS. Bake refuses MIN_IDLE_HOURS=0 unless ALLOW_MIN_IDLE_ZERO=1 (lab only).

1. Prepare production config on the build host

cp config/cotel.env.example config/cotel.env
$EDITOR config/cotel.env

Set org values: BUCKET, KMS_KEY_ID, AWS_REGION, VAULT_AWS_MOUNT, VAULT_AWS_ROLE, COTEL_CERT_SUFFIX, and MIN_IDLE_HOURS=48.

config/cotel.env holds deployment identity only — not AWS secrets.

2. Build signed, config-baked packages (both architectures)

# Bump VERSION (+ CHANGELOG) when shipping an upgrade — MDM compares pkg receipt version

export INSTALLER_IDENTITY="Developer ID Installer: Your Org (TEAMID)"  # optional but recommended
export REQUIRE_BAKED_COTEL_ENV=1   # fail the build if config/cotel.env is missing

# Via make:
REQUIRE_BAKED_COTEL_ENV=1 ARCH=arm64  make pkg-macos
# → dist/claude-otel-<VERSION>-arm64.pkg

REQUIRE_BAKED_COTEL_ENV=1 ARCH=x86_64 make pkg-macos
# → dist/claude-otel-<VERSION>-x86_64.pkg

# Or call the builder directly:
# ARCH=arm64  bash install/macos/pkg/build-pkg.sh

Optional deploy tarball (.pkg + install / uninstall / preflight for scripted ops):

REQUIRE_BAKED_COTEL_ENV=1 make bundle-macos
# → dist/claude-otel-<VERSION>-<arch>-bundle.tar.gz

3. Distribute via MDM

  1. Create/update a Custom App (or equivalent) library item.
  2. Attach both architecture packages (Apple Silicon + Intel) when needed.
  3. Set the app / package version to match repo VERSION.
  4. Assign after fleet prereqs are present on devices:
    • HashiCorp vault CLI
    • vault-connector
    • Org device cert (CN matching COTEL_CERT_SUFFIX)
    • Claude Code

Optional companion scripts:

  • Audit — pkgutil --pkg-info com.customer.claude-otel, ports 4317/16686, command -v vault vault-connector, test -f …/config/cotel.env
  • Uninstall / offboard — install/macos/uninstall.sh (or the deploy bundle)
# Full wipe (system payload + console-user data):
sudo bash install/macos/uninstall.sh
# All local users + strip OTEL keys from ~/.claude/settings.json:
sudo bash install/macos/uninstall.sh --all-users --strip-claude-settings
# Keep JSONL / export state:
sudo bash install/macos/uninstall.sh --keep-data

4. Force a config rewrite on existing Macs

Upgrades do not overwrite /Library/Application Support/claude-otel/config/cotel.env. To push new baked values fleet-wide:

# MDM Custom Script (before or with the upgraded package), or:
sudo rm -f "/Library/Application Support/claude-otel/config/cotel.env"
# then reinstall / re-enforce the Custom App so postinstall re-seeds from cotel.env.default

Or ship a separate MDM script that writes cotel.env after package install.

Lab rebuild (local, not fleet)

# MIN_IDLE_HOURS=0 in config/cotel.env for dry-runs
ALLOW_MIN_IDLE_ZERO=1 make pkg-macos
ALLOW_MIN_IDLE_ZERO=1 make install-macos

Common make targets

make help               # full list
make preflight-macos    # vault / vault-connector / python3 / …
make install-macos      # preflight + build + silent installer
make uninstall-macos    # full macOS wipe
make pkg-macos          # build .pkg only
make bundle-macos       # deploy tarball
make install-linux      # systemd user units (no Panther sync)
make start / stop       # Linux only
make status / verify
make clean

Fleet bake shortcut:

REQUIRE_BAKED_COTEL_ENV=1 make pkg-macos

Key artifacts

File Purpose
scripts/cotel-panther-export.py Session aggregator — one claude_code_session record per eligible session
scripts/cotel-s3-upload.py Stdlib SigV4 S3 PutObject (no AWS CLI)
scripts/sync.sh Orchestrator — Vault creds, export, upload, confirm, prune
config/cotel.env.example Template for sync deployment settings (copy to config/cotel.env)
infra/telemetry-bucket.tf S3 bucket, KMS, IAM writer role, Panther read policy
install/macos/LaunchAgents/*.plist Static LaunchAgents (pkg payload; every 4h sync)
install/macos/pkg/build-pkg.sh Builds S1-safe .pkg; fleet-bakes cotel.env → cotel.env.default
install/macos/pkg/build-bundle.sh Deploy tarball: .pkg + install / uninstall / preflight
install/macos/install.sh Silent install via installer -pkg (preflight + vault checks)
install/macos/preflight.sh Host dependency checks before install
install/macos/uninstall.sh Full macOS wipe (--keep-data, --all-users, --strip-claude-settings)
docs/macos-pkg-install.md Package install, preflight, uninstall, S1 rationale
docs/kandji-mdm-deployment.md MDM / Kandji zero-touch + fleet bake details

Panther pipeline summary

  • Eligibility: sessions idle ≥ MIN_IDLE_HOURS (default 48)
  • S3 key: YYYY-MM-DD/{device_cn}/{session_id[:8]}-{last_ts}.jsonl.gz
  • State file: ~/.config/claude-otel/panther-export-state.json
  • Sync config: config/cotel.env (or packaged path under /Library/Application Support/claude-otel/config/)
  • Encryption: SSE-KMS when KMS_KEY_ID is a real key/alias; otherwise SSE-S3 (AES256)
  • Prune: after confirmed upload, keeps logs.jsonl inode so the collector is not orphaned

Example claude_code_session record

Exported from synthetic examples/samples/logs-sample.jsonl. actions truncated.

{
  "record_type": "claude_code_session",
  "schema_version": 1,
  "session_id": "11111111-…example…",
  "session_start_time": "2026-04-21T22:03:15.234Z",
  "session_last_event_time": "2026-04-21T22:04:32.411Z",
  "device_cn": "example-device.example.com",
  "service_name": "claude-code",
  "service_version": "2.1.116",
  "turn_count": 1,
  "total_cost_usd": 0.400184,
  "total_tool_calls": 4,
  "total_api_requests": 8,
  "turns": [
    {
      "record_type": "turn",
      "turn_number": 1,
      "prompt_text": "aho W0 OTEL probe",
      "actions": [
        { "order": 1, "action_type": "api_call", "model": "claude-haiku-4-5-20251001" },
        { "order": 2, "action_type": "api_call", "model": "claude-opus-4-7" },
        { "order": 3, "action_type": "tool_call", "tool_name": "Glob" },
        { "order": 4, "action_type": "tool_call", "tool_name": "Read" }
      ],
      "api_errors": []
    }
  ]
}

Further reading

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages