Цель. Собирать централизованную статистику использования Claude Code по команде на одном сервере: стоимость, продуктивность, аномалии.
Подписка. Claude.ai Teams (даёт server-managed settings через ~/.claude/remote-settings.json).
Источник. Built-in OpenTelemetry экспорт Claude Code → OTLP/HTTP JSON endpoint в любом backend-сервисе → реляционная БД.
Прагматичный путь. Берём существующий backend-сервис (домен, TLS, CI/CD, БД уже есть) и добавляем туда два endpoint-а. Никакой отдельной инфры (Collector / Prometheus / Grafana / Docker / nginx) на старте. Heavy stack — опциональное расширение.
1. Архитектура
1.1 MVP — backend endpoint + БД
┌────────────────────────┐ ┌──────────────────────────────────┐
│ Dev laptop × N │ │ Existing backend service │
│ │ │ │
│ Claude Code │ OTLP │ │
│ ├─ metrics ──────────┼─ HTTPS ─┼─► POST /otel/v1/metrics │
│ └─ logs/events ──────┼─ JSON ──┼─► POST /otel/v1/logs │
│ │ Bearer │ │ │
│ remote-settings.json │◄── pull │ ▼ │
│ (env block, hourly) │ claude.ai ┌───────────────────────────┐ │
└────────────────────────┘ │ │ DB: claude_code_* │ │
│ │ - otlp_raw (JSON blob) │ │
│ │ - metric_points (parsed) │ │
│ │ - events (parsed) │ │
│ └───────────────────────────┘ │
│ │ │
│ │ scheduled парсер 1/мин │
│ ▼ │
│ отчёты SQL / Metabase / Grafana │
└──────────────────────────────────┘Стек на стороне сервера не важен — Node, Python, Go, Java, Rust, что есть. Нужны только: HTTP endpoint с Bearer-проверкой, INSERT в БД, периодическая задача-парсер.
1.2 Heavy alt — отдельный OTel Collector + Prometheus + Grafana
Опционально позже если БД не тянет или нужны готовые дашборды.
Dev laptop × N ──OTLP/gRPC──► OTel Collector ──► Prometheus ──► Grafana1.3 Ключевые решения
- JSON, не protobuf. Claude Code умеет
OTEL_EXPORTER_OTLP_PROTOCOL=http/json— никаких protobuf-зависимостей на сервере. - Push, не pull. Каждый ноут шлёт OTLP/HTTP. Не открываем порты на ноутах.
- Один Bearer-токен на команду. Per-user не нужны —
user.emailберётся из OAuth claude.ai. - Сырое хранение + ленивый парсер. Сначала JSON в
otlp_raw, потом scheduled job раскладывает в типизованные таблицы. Схема OTLP может меняться — raw остаётся как audit trail. - Конфиг ноутов через Teams admin console. Один JSON, прилетает всем за час, без bootstrap-скриптов.
2. Что собирается
2.1 Resource attributes (на каждой метрике/событии)
| Атрибут | Описание | Контроль |
|---|---|---|
service.name | "claude-code" | всегда |
service.version / app.version | версия CLI | OTEL_METRICS_INCLUDE_VERSION |
os.type, os.version, host.arch | OS + CPU | всегда |
wsl.version | только на WSL | авто |
session.id | UUID сессии | OTEL_METRICS_INCLUDE_SESSION_ID (default on) |
organization.id | ID Teams-организации | при auth |
user.account_uuid, user.account_id | UUID аккаунта Anthropic | OTEL_METRICS_INCLUDE_ACCOUNT_UUID (default on) |
user.id | анонимный device ID | всегда |
user.email | email из OAuth | при OAuth-логине |
terminal.type | iTerm / vscode / cursor / tmux | авто |
prompt.id | UUID, связывает события одного промпта | только в events |
workspace.host_paths | пути из desktop app | только в events |
Через OTEL_RESOURCE_ATTRIBUTES можно прилеплять кастомные labels (team, environment, cost_center и т.п.).
2.2 Метрики (8)
| Метрика | Тип | Unit | Триггер | Атрибуты |
|---|---|---|---|---|
claude_code.session.count | counter | count | старт сессии | start_type: fresh/resume/continue |
claude_code.lines_of_code.count | counter | count | правка кода | type: added/removed |
claude_code.pull_request.count | counter | count | создание PR | — |
claude_code.commit.count | counter | count | git commit | — |
claude_code.cost.usage | counter | USD | каждый API request | model, query_source, speed, effort |
claude_code.token.usage | counter | tokens | каждый API request | type, model, query_source, speed, effort |
claude_code.code_edit_tool.decision | counter | count | accept/reject правки | tool_name, decision, source, language |
claude_code.active_time.total | counter | sec | активность user/CLI | type: user/cli |
2.3 События / Logs (18)
Все события несут event.name, event.timestamp, event.sequence, prompt.id, workspace.host_paths плюс resource attrs.
| Event | Триггер | Ключевые атрибуты |
|---|---|---|
claude_code.user_prompt | юзер отправил промпт | prompt_length, command_name; prompt только с OTEL_LOG_USER_PROMPTS=1 |
claude_code.tool_result | tool завершён | tool_name, tool_use_id, success, duration_ms, error_type, размеры I/O |
claude_code.api_request | успешный API call | model, cost_usd, duration_ms, токены, request_id, speed, effort |
claude_code.api_error | API fail | model, error, status_code, duration_ms, attempt |
claude_code.api_retries_exhausted | финальный fail | total_attempts, total_retry_duration_ms |
claude_code.api_request_body | только с OTEL_LOG_RAW_API_BODIES | вся переписка с моделью |
claude_code.api_response_body | только с OTEL_LOG_RAW_API_BODIES | ответ модели |
claude_code.tool_decision | accept/reject permission | tool_name, decision, source |
claude_code.permission_mode_changed | смена режима | from_mode, to_mode, trigger |
claude_code.auth | /login или /logout | action, success, auth_method |
claude_code.mcp_server_connection | MCP connect/disconnect/fail | status, transport_type, duration_ms |
claude_code.internal_error | внутренний exception | error_name, error_code (без message/stack) |
claude_code.plugin_installed | плагин установлен | marketplace.is_official, install.trigger |
claude_code.skill_activated | skill вызван | skill.name, invocation_trigger |
claude_code.at_mention | resolved @mention | mention_type, success |
claude_code.hook_execution_start | хуки начали выполняться | hook_event, hook_name, num_hooks |
claude_code.hook_execution_complete | хуки завершены | + num_success, num_blocking, total_duration_ms |
claude_code.compaction | сжатие контекста | trigger, success, pre_tokens, post_tokens |
2.4 Traces (бета)
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1. Спаны: tool execution, API request, hook execution. Для базовой задачи не нужны — большой объём, метрик и событий хватает.
2.5 Ссылки
- Полная справка: https://code.claude.com/docs/en/monitoring-usage.md
- Server-managed settings: https://code.claude.com/docs/en/server-managed-settings.md
- Admin setup: https://code.claude.com/docs/en/admin-setup.md
- Settings reference: https://code.claude.com/docs/en/settings.md
3. PII и приватность
Что уходит по умолчанию
- Метрики и события без содержимого: длины, длительности, счётчики, типы операций, токены, стоимость, имена моделей и инструментов.
user.email— основной идентификатор пользователя.user.account_uuid,user.id,organization.id,session.id,prompt.id.- Имена встроенных tools и команд (Read, Write, Bash, Edit и т.д.).
Что НЕ уходит без явного opt-in
| Данные | По умолчанию | Включается |
|---|---|---|
| Текст промптов | length only | OTEL_LOG_USER_PROMPTS=1 |
| Tool args (Bash команды, URL, паттерны) | — | OTEL_LOG_TOOL_DETAILS=1 (~4 KB cap) |
| File paths | в tool events | OTEL_LOG_TOOL_DETAILS=1 |
| File contents / Bash output | — | OTEL_LOG_TOOL_CONTENT=1 (60 KB cap, только в traces) |
| API request body | — | OTEL_LOG_RAW_API_BODIES=1 |
| API response body | — | OTEL_LOG_RAW_API_BODIES |
| Имена custom skills/plugins | collapsed | OTEL_LOG_TOOL_DETAILS=1 |
| Extended-thinking | всегда редактится | нельзя включить |
Согласие пользователей
Согласие не запрашивается Claude Code-ом. Конфиг применяется молча через remote-settings.json. Юзер может узнать о телеметрии только посмотрев файл руками.
Поэтому ответственность на админах:
- До раскатки уведомить команду: что собираем, где хранится, кто имеет доступ.
- Указать в onboarding-доке.
- Если включаются
OTEL_LOG_TOOL_DETAILSилиOTEL_LOG_RAW_API_BODIES— отдельное явное оповещение, поскольку утечь могут пароли в Bash args, токены в URL, содержимое файлов.
Рекомендуемый baseline
Только метаданные. Без opt-in флагов. Достаточно для всех бизнес-метрик (cost / productivity / adoption / health). PII = только user.email.
4. Конфиг для разработчиков (через Teams admin console)
4.1 Где править
claude.ai → Admin → Settings → Claude Code → Managed settings → текстовое поле, в которое вставляется JSON.
4.2 JSON для пуша
К существующему конфигу добавляется блок env. Endpoint указывает на ваш backend-сервис:
{
"env": {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
"OTEL_METRICS_EXPORTER": "otlp",
"OTEL_LOGS_EXPORTER": "otlp",
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://<your-backend>/otel",
"OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Bearer REPLACE_WITH_REAL_TOKEN",
"OTEL_METRIC_EXPORT_INTERVAL": "30000",
"OTEL_LOGS_EXPORT_INTERVAL": "5000",
"OTEL_RESOURCE_ATTRIBUTES": "team=backend,environment=prod"
}
}Claude Code сам добавит суффиксы
/v1/metricsи/v1/logsкOTEL_EXPORTER_OTLP_ENDPOINT— endpoint указывает только на префикс/otel.
4.3 Поведение
- Прилетает на
~/.claude/remote-settings.jsonкаждого пользователя. - Sync: при логине + раз в час.
- Локальные правки
remote-settings.jsonзатираются на следующем sync. envиз server-managed побеждает локальный~/.claude/settings.json(выше по precedence).
4.4 Если admin console не покажет поле для env
Альтернатива — ~/.claude/settings.json-шаблон + onboarding-скрипт, либо managed-settings.json через MDM. Менее удобно, но работает (см. раздел 8).
5. Серверная часть
5.1 Нагрузка
Для команды ~25 человек:
- ~25 пушей метрик в минуту (
OTEL_METRIC_EXPORT_INTERVAL=30s) - ~25 пушей логов каждые 5с (
OTEL_LOGS_EXPORT_INTERVAL=5s) - Размер payload: 5–50 KB сжатого JSON
- Итого: ~5 RPS пиково, ~50 MB/день сырого JSON
Любая реляционная БД (PostgreSQL / MySQL / MariaDB) спокойно тянет, дополнительная нагрузка минимальна.
5.2 Schema — две таблицы (raw + parsed)
DDL ниже на MySQL. Под Postgres — JSONB вместо JSON, BIGSERIAL вместо BIGINT UNSIGNED AUTO_INCREMENT, TIMESTAMP(3) вместо DATETIME(3). Семантика та же.
Raw bucket — складываем как есть, парсим потом:
CREATE TABLE claude_code_otlp_raw (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
received_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
kind VARCHAR(16) NOT NULL, -- 'metrics' | 'logs'
payload JSON NOT NULL,
parsed_at DATETIME(3) NULL, -- метка батч-парсера
INDEX idx_received (received_at),
INDEX idx_unparsed (parsed_at, id)
) ENGINE=InnoDB
ROW_FORMAT=COMPRESSED
DEFAULT CHARSET=utf8mb4;Метрики (типизованные точки):
CREATE TABLE claude_code_metric_points (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
raw_id BIGINT UNSIGNED NOT NULL,
ts DATETIME(3) NOT NULL,
user_email VARCHAR(255),
user_id VARCHAR(64),
session_id VARCHAR(64),
org_id VARCHAR(64),
metric_name VARCHAR(128) NOT NULL,
value DOUBLE NOT NULL,
model VARCHAR(64),
query_source VARCHAR(32),
speed VARCHAR(16),
effort VARCHAR(16),
type VARCHAR(32),
INDEX idx_ts (ts),
INDEX idx_user_metric (user_email, metric_name, ts),
INDEX idx_metric_ts (metric_name, ts)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;События:
CREATE TABLE claude_code_events (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
raw_id BIGINT UNSIGNED NOT NULL,
ts DATETIME(3) NOT NULL,
event_name VARCHAR(64) NOT NULL,
user_email VARCHAR(255),
session_id VARCHAR(64),
prompt_id VARCHAR(64),
attrs JSON,
INDEX idx_ts (ts),
INDEX idx_event_ts (event_name, ts),
INDEX idx_user_event (user_email, event_name, ts)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;5.3 Endpoint и парсер — два компонента
Реализация на любом стеке. Логика идентична:
Ingestion endpoint — принимает POST на /otel/v1/metrics и /otel/v1/logs:
- Проверить заголовок
Authorization: Bearer <token>против ожидаемого. На несовпадение —401. - INSERT body как есть в
claude_code_otlp_raw(kind =metricsилиlogs). - Ответить
200 {}. Без парсинга в hot path.
Парсер — periodic job (cron / scheduler) каждую минуту:
- SELECT N сырых записей с
parsed_at IS NULL(батч 500–2000). - Для каждой:
- JSON.parse → пройтись по
resourceMetrics/resourceLogs. - Для метрик: вытащить из
dataPointsимя, значение, timestamp + атрибуты ресурса и точки. INSERT вclaude_code_metric_points. - Для логов: вытащить
event.name, timestamp, атрибуты. INSERT вclaude_code_events(остальное в JSON-колонкуattrs).
- JSON.parse → пройтись по
- UPDATE
parsed_at = NOW()на обработанных.
Атрибуты в OTLP лежат как массив [{"key":"...","value":{"stringValue":"..."}}]. Хелпер раскладывает в Map<String, String>, перебирая stringValue / intValue / doubleValue / boolValue.
5.4 TTL — крон-удаление старых
Простейший вариант — отдельная scheduled-задача в самом приложении: раз в час DELETE FROM claude_code_otlp_raw WHERE received_at < NOW() - INTERVAL 90 DAY LIMIT 50000. Раз в день — то же для типизованных таблиц с горизонтом 365 дней.
В MySQL можно через EVENT (event_scheduler = ON), в Postgres — через pg_cron. Внешний cron + psql/mysql тоже годится.
5.5 Развёртывание — пошагово
- Сгенерировать токен:
openssl rand -hex 32→ положить в secret store. - Применить миграцию (Flyway / Liquibase / Alembic / sqlx-migrate) с тремя таблицами.
- Добавить ingestion endpoint + парсер в код приложения.
- Прокинуть токен в конфиг приложения через переменную окружения / secret manager. Никогда не коммитить.
- Деплой через CI.
- Smoke-тест с локальной машины:
curl -X POST https://<your-backend>/otel/v1/metrics \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"resourceMetrics":[]}' # ожидаем 200 {} - Включить env-блок в Teams admin console (раздел 4.2). В течение часа метрики начнут приходить.
- Проверить через час:
SELECT COUNT(*), MIN(received_at), MAX(received_at) FROM claude_code_otlp_raw;.
6. Безопасность
| Угроза | Митигация |
|---|---|
| Сторонний пуш на endpoint | Bearer-токен в Authorization, проверка в endpoint |
| Перехват трафика | TLS на домене (или managed proxy) |
| Утечка токена | Один shared токен, ротация через admin console + перевыпуск в secret store |
| Доступ к данным в БД | Существующие права доступа (read-only роль для аналитики) |
| PII утечки в метриках | Не включать OTEL_LOG_* флаги без явного оповещения команды |
| Bash args с секретами | Если включён OTEL_LOG_TOOL_DETAILS — фильтр в парсере (regex маска токенов / паролей перед сохранением) |
| DoS на endpoint | Rate-limit на уровне приложения (token bucket) или reverse-proxy перед ним |
Опциональный hardening:
- IP whitelist на уровне API gateway (если разработчики через VPN).
- Алерт на падение
INSERT-RPS вclaude_code_otlp_rawза 1 час в рабочее время — индикатор поломки телеметрии или массового отключения. - Ротация токена: при компрометации генерим новый, обновляем в admin console — за час разъедется. Старый оставить валидным ещё час (overlap), потом отрубить.
7. Отчёты — SQL
Запросы работают по claude_code_metric_points и claude_code_events. Можно крутить из любого SQL-клиента (DataGrip, Metabase, Apache Superset, CLI). Готовые дашборды в Grafana — позже через MySQL/Postgres datasource.
7.1 Cost
-- суммарная стоимость за сутки
SELECT ROUND(SUM(value), 2) AS cost_usd
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.cost.usage'
AND ts >= NOW() - INTERVAL 1 DAY;
-- топ-10 пользователей по тратам за неделю
SELECT user_email, ROUND(SUM(value), 2) AS cost_usd
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.cost.usage'
AND ts >= NOW() - INTERVAL 7 DAY
GROUP BY user_email
ORDER BY cost_usd DESC
LIMIT 10;
-- стоимость по моделям за сутки
SELECT model, ROUND(SUM(value), 2) AS cost_usd
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.cost.usage'
AND ts >= NOW() - INTERVAL 1 DAY
GROUP BY model
ORDER BY cost_usd DESC;
-- стоимость по effort level
SELECT effort, ROUND(SUM(value), 2) AS cost_usd
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.cost.usage'
AND ts >= NOW() - INTERVAL 1 DAY
GROUP BY effort;7.2 Tokens
-- токены по типам за час
SELECT type, SUM(value) AS tokens
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.token.usage'
AND ts >= NOW() - INTERVAL 1 HOUR
GROUP BY type;
-- cache hit ratio
SELECT
SUM(CASE WHEN type = 'cacheRead' THEN value ELSE 0 END)
/ SUM(CASE WHEN type IN ('input', 'cacheRead') THEN value ELSE 0 END) AS cache_hit_ratio
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.token.usage'
AND ts >= NOW() - INTERVAL 1 HOUR;7.3 Productivity
-- строк добавлено/удалено по людям за неделю
SELECT user_email, type, SUM(value) AS lines
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.lines_of_code.count'
AND ts >= NOW() - INTERVAL 7 DAY
GROUP BY user_email, type
ORDER BY user_email;
-- accept rate правок
SELECT
SUM(CASE WHEN type LIKE 'accept%' THEN value ELSE 0 END)
/ SUM(value) AS accept_rate
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.code_edit_tool.decision'
AND ts >= NOW() - INTERVAL 1 DAY;7.4 Engagement
-- активное время в минутах по людям за день
SELECT user_email, ROUND(SUM(value) / 60) AS minutes
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.active_time.total'
AND ts >= NOW() - INTERVAL 1 DAY
GROUP BY user_email
ORDER BY minutes DESC;
-- DAU за неделю
SELECT DATE(ts) AS day, COUNT(DISTINCT user_email) AS dau
FROM claude_code_metric_points
WHERE metric_name = 'claude_code.session.count'
AND ts >= NOW() - INTERVAL 7 DAY
GROUP BY day
ORDER BY day;7.5 Reliability
-- API error rate за последний час
SELECT
SUM(CASE WHEN event_name = 'claude_code.api_error' THEN 1 ELSE 0 END)
/ COUNT(*) AS error_rate
FROM claude_code_events
WHERE event_name IN ('claude_code.api_request', 'claude_code.api_error')
AND ts >= NOW() - INTERVAL 1 HOUR;
-- топ ошибок tools
SELECT
JSON_UNQUOTE(JSON_EXTRACT(attrs, '$.tool_name')) AS tool,
JSON_UNQUOTE(JSON_EXTRACT(attrs, '$.error_type')) AS error,
COUNT(*) AS cnt
FROM claude_code_events
WHERE event_name = 'claude_code.tool_result'
AND JSON_EXTRACT(attrs, '$.success') = 'false'
AND ts >= NOW() - INTERVAL 1 DAY
GROUP BY tool, error
ORDER BY cnt DESC
LIMIT 20;7.6 Алерты
Реализация — scheduled-задача в самом приложении или внешний cron, шлёт в Slack webhook / email.
| Алерт | SQL-условие | Severity |
|---|---|---|
| Daily cost spike | сегодняшняя стоимость > 2× от среднего за 7 дней | warning |
| Per-user cost cap | любой юзер > $100/day | warning |
| API error rate | ошибок > 5% от запросов за 15 мин | critical |
| Telemetry silence | 0 INSERT в otlp_raw за 1 час в рабочее время | warning |
| Parser stuck | необработанных строк (parsed_at IS NULL) > 10000 | warning |
8. Альтернативы распространению конфига
Если Teams admin console не позволит пушить env (UI ограничен только plugins/permissions):
8.1 ~/.claude/settings.json через onboarding
Bootstrap-скрипт мерджит env-блок в локальный ~/.claude/settings.json каждого разработчика. Минусы: разработчик может удалить, забыть, не запустить.
8.2 managed-settings.json через MDM
Системный путь, защищён правами root:
- macOS:
/Library/Application Support/ClaudeCode/managed-settings.json - Linux:
/etc/claude-code/managed-settings.json
Раскатка через Jamf / Ansible / Puppet. Перебить локально нельзя. Хорошо для жёсткого compliance, требует MDM-инфры.
8.3 Сравнение
| Способ | Принуждение | Усилия | Per-user токены | Рекомендация |
|---|---|---|---|---|
Teams admin console (remote-settings.json) | сильное | минимум | нет | первый выбор |
MDM (managed-settings.json) | максимальное | высокие | возможно через шаблоны | если есть MDM |
Bootstrap-скрипт + ~/.claude/settings.json | слабое | низкие | да | fallback |
9. Расширения (когда понадобится)
9.1 Grafana поверх БД
Когда захочется красивых дашбордов:
- Поднять Grafana (отдельный контейнер или managed).
- Добавить datasource (MySQL / Postgres) → подключить к существующей БД с read-only ролью.
- Импортировать дашборды (или собрать из SQL раздела 7).
Без переноса данных, без OTel Collector, без Prometheus.
9.2 Переезд на OTel Collector + Prometheus
Если объём событий превысит ~1M/день или захочется готовых OTel-инструментов:
Claude Code → OTel Collector (auth) → ClickHouse / Prometheus + GrafanaСвой endpoint остаётся как fallback / staging. Конфиг ноутов меняется в admin console на новый endpoint.
9.3 Логи в Loki / отдельный лог-стек
При росте claude_code_events парсенные события можно дублировать в Loki через Vector / Fluent Bit. Лучше для full-text поиска по содержимому промптов (если когда-то включится OTEL_LOG_USER_PROMPTS=1).
9.4 Grafana Cloud как замена self-hosted
Free tier (10k серий, 50 GB логов) покрывает ~25 человек. Только OTel Collector локально или прямой OTLP push в Grafana Cloud. Минус — данные снаружи, конфликт с compliance.
9.5 Per-user токены
Если потребуется отзывать доступ point-wise:
- Сгенерировать N токенов, сохранить в Vault / SSM.
- Onboarding-скрипт читает токен и кладёт в
~/.claude/settings.json. - В endpoint — валидация против таблицы
claude_code_telemetry_tokens(token_hash, user_id, revoked_at).
Для команды до ~30 человек обычно overkill. Один shared токен + ротация при компрометации проще.
9.6 Обогащение бизнес-контекстом
В парсере при сохранении в типизованные таблицы добавить JOIN с внутренней таблицей users (если email сматчится) и складывать team, department, cost_center, tenure_days. Полезно для декомпозиции ROI по командам.
9.7 Защита от утечек в Bash args
Если включён OTEL_LOG_TOOL_DETAILS=1, в парсере перед сохранением — regex-маска по типичным паттернам секретов (password|token|secret|api[_-]?key|bearer + значение → [REDACTED]).
10. Roadmap
| Этап | Шаги | Оценка |
|---|---|---|
| MVP | миграция БД + ingestion endpoint + парсер в существующий backend, env-блок в admin console | 0.5 дн. |
| Отчёты | базовые SQL из раздела 7, Metabase/Superset/Grafana поверх БД | 0.5–1 дн. |
| Соглашение по приватности | анонс команде, обновление wiki, описание состава данных | 0.5 дн. |
| Алерты | scheduled-проверки + Slack webhook (cost spike, error rate, parser stuck) | 0.5 дн. |
| Расширение (опц.) | Grafana дашборды, OTel Collector для масштаба, JOIN с users для team-разреза | 1–2 дн. |
Critical path: ~1 рабочий день от старта до рабочих SQL-отчётов.
11. Чеклист перед раскаткой
- Миграция БД создана (
claude_code_otlp_raw,claude_code_metric_points,claude_code_events) - Bearer-токен сгенерирован (
openssl rand -hex 32) и сохранён в secret store - Endpoint и парсер добавлены в код, покрыты unit-тестами
- Деплой через CI прошёл, миграция применилась
- Smoke-тест
curlна/otel/v1/metricsотвечает 200 - Команда уведомлена о телеметрии и составе данных
- Admin console обновлён с env-блоком (раздел 4.2)
- Через 1 час: в
otlp_raw≥3 разныхuser.email - Парсер отрабатывает:
parsed_at IS NULLстабильно низкий - Базовые отчёты из раздела 7 возвращают непустые результаты
- Алерты настроены (cost spike, error rate, telemetry silence, parser stuck)
- TTL/cleanup задачи настроены
12. Маскировка промптов до отправки в Anthropic — UserPromptSubmit hook
Отдельная задача от телеметрии. Здесь речь не о том, что Claude Code шлёт нам в OTel, а о том, что юзер шлёт в Anthropic API. Если в чате упоминаются секреты, бренды клиентов, внутренние URL — они уйдут в облако. Hook позволяет перехватить и отредактировать промпт до отправки.
12.1 Механизм
UserPromptSubmit — встроенный hook Claude Code, срабатывает между Enter и API-вызовом.
| Канал | Описание |
|---|---|
| stdin | JSON: prompt, cwd, session_id, transcript_path |
| Exit 0 | промпт идёт дальше как есть |
| Exit 2 | промпт блокируется, stderr показывается юзеру как ошибка |
| JSON на stdout | {"decision":"approve","modifiedPrompt":"..."} — заменить текст; {"decision":"block","reason":"..."} — отказать |
12.2 Скрипт-санитайзер (пример)
#!/usr/bin/env bash
set -euo pipefail
INPUT=$(cat)
PROMPT=$(echo "$INPUT" | jq -r '.prompt')
REDACTED=$(echo "$PROMPT" | sed -E \
-e 's/(password|passwd|secret|token|api[_-]?key|bearer)[[:space:]=:"'"'"']+[^[:space:]"]+/\1=[REDACTED]/gi' \
-e 's/AKIA[0-9A-Z]{16}/[AWS_KEY_REDACTED]/g' \
-e 's/AIza[0-9A-Za-z_-]{35}/[GCP_KEY_REDACTED]/g' \
-e 's/eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/[JWT_REDACTED]/g' \
-e 's/(jdbc|postgres|postgresql|mysql|mongodb|redis):\/\/[^[:space:]"'"'"']+/\1:\/\/[CONN_REDACTED]/gi')
if [ "$PROMPT" != "$REDACTED" ]; then
jq -n --arg p "$REDACTED" '{decision:"approve", modifiedPrompt:$p}'
fi
exit 0Список бренд-имён и доменов клиентов — собрать с legal/security и обновлять централизованно.
12.3 Раскатка через Teams admin console
Hooks поддерживаются в server-managed settings. Расширяем JSON из раздела 4.2:
{
"env": { "...": "..." },
"hooks": {
"UserPromptSubmit": [
{
"matcher": "*",
"hooks": [
{ "type": "command", "command": "/usr/local/bin/redact-prompt" }
]
}
]
}
}Сам скрипт раздаём через MDM / dotfiles repo / пакетный менеджер в /usr/local/bin/ или ~/.local/bin/. Если admin console не пушит сам бинарник — нужен onboarding-шаг.
12.4 Ограничения hook-а
| Кейс | Поведение |
|---|---|
@file mentions | резолвятся после хука — содержимое файла хуку не видно |
| Drag-drop изображений / binary | в .prompt только текст, binary хуку недоступен |
| Системный prompt + tool results | не видит, только пользовательский ввод |
| Slash-commands | видит строку /command args |
| Followup-сообщения | срабатывает на каждом prompt-submit |
| Скорость | синхронный, держать <50ms — иначе тормозит UX |
12.5 Дополнительные хуки для защиты
UserPromptSubmit режет утечку через текст. Файлы и команды нужно закрывать отдельно:
| Hook | Что блокирует | Пример |
|---|---|---|
PreToolUse на Read | чтение чувствительных путей | .env, ~/.aws/credentials, ~/.ssh/, /etc/shadow |
PreToolUse на Bash | команды с чувствительными paths/args | cat ~/.aws/*, printenv, env, gcloud auth print-access-token |
PreToolUse на Edit / Write | запись секретов в коммитимые файлы | .env без .gitignore, hardcoded keys |
12.6 Что хук НЕ закрывает
- Уже отправленные сообщения в текущей сессии — нельзя retro-редактить.
- Содержимое файлов прочитанных через
Readили@mention— хук работает только над текстом промпта. Утечка файлов с секретами решается черезPreToolUse:Read. - Транскрипт в
~/.claude/projects/<dir>/sessions/*.jsonlхранится сырым на ноуте — секреты в нём останутся локально.
12.7 Связь с телеметрией
- Если
OTEL_LOG_USER_PROMPTS=0(baseline) — оригинальный промпт не уходит в OTel-БД. Хук защищает только от утечки в Anthropic. - Если когда-то включится
OTEL_LOG_USER_PROMPTS=1— Claude Code пишет в телеметрию уже модифицированный промпт (после хука). Санитайзер автоматом защитит и Anthropic, и нашу БД.
12.8 Документация
- Hooks reference: https://code.claude.com/docs/en/hooks
- Server-managed hooks: https://code.claude.com/docs/en/server-managed-settings.md
