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).
claude-otel provides:
- 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.
- Configuration for Claude Code (via generated
.claude/settings.json) to emit rich telemetry (user prompts, tool calls, costs, tokens, MCP, api requests, etc.) withcotel.*resource attributes for attribution. - 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).
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 | 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
| 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 |
| 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| 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.
- systemd user session available
- No Vault / device cert required (no Panther sync on Linux)
cp config/cotel.env.example config/cotel.env
$EDITOR config/cotel.envconfig/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) |
# 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 wipeSilent 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"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-exportFor 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
| 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).
cp config/cotel.env.example config/cotel.env
$EDITOR config/cotel.envSet 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.
# 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.shOptional 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- Create/update a Custom App (or equivalent) library item.
- Attach both architecture packages (Apple Silicon + Intel) when needed.
- Set the app / package version to match repo
VERSION. - 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-dataUpgrades 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.defaultOr ship a separate MDM script that writes cotel.env after package install.
# 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-macosmake 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 cleanFleet bake shortcut:
REQUIRE_BAKED_COTEL_ENV=1 make pkg-macos| 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 |
- 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_IDis a real key/alias; otherwise SSE-S3 (AES256) - Prune: after confirmed upload, keeps
logs.jsonlinode so the collector is not orphaned
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": []
}
]
}- docs/getting-started.md — full setup and configuration checklist
- docs/macos-pkg-install.md — S1-safe package install, preflight, uninstall
- docs/kandji-mdm-deployment.md — fleet / MDM zero-touch bake
design-0.2.0.md— authoritative spec, schemas, verificationdocs/claude-otel-0.2.0.md— architecture diagrams (GitHub-rendered)docs/privacy-profiles.md— telemetry richness levelsCHANGELOG.md