Add session analytics via Claude Code OpenTelemetry#137
Merged
dhilgaertner merged 5 commits intomainfrom Apr 21, 2026
Merged
Conversation
47f9529 to
1360e14
Compare
Adds an in-process OTLP HTTP/JSON receiver so Crow can ingest telemetry from Claude Code sessions and surface per-session analytics in the UI. New CrowTelemetry package: - OTLP HTTP receiver using NWListener on localhost - SQLite storage for metrics and events - TelemetryService facade coordinating receiver + database Integration: - Injects OTEL env vars when launching Claude Code sessions - Maps sessions via OTEL_RESOURCE_ATTRIBUTES=crow.session.id - Analytics strip in session detail header (cost, tokens, tools, time) - Telemetry settings in preferences (enable/disable, port config) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two bugs fixed: 1. New sessions via `crow send` were missing OTEL env vars entirely. The send RPC handler now injects them when it detects a managed terminal receiving a claude command (same path that writes hooks). 2. The telemetry onDataReceived callback referenced self.telemetryService which was nil because it was assigned after start(). Restructured so init + assignment happens synchronously before start() runs in a Task. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
50c9087 to
766ba88
Compare
Users enable session analytics via Settings → General → Telemetry. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dgershman
approved these changes
Apr 16, 2026
Collaborator
dgershman
left a comment
There was a problem hiding this comment.
Code & Security Review
Critical Issues
None found — this is a well-structured feature addition.
Security Review
Strengths:
- OTLP receiver binds to
NWEndpoint.hostPort(host: .ipv4(.loopback))withparams.acceptLocalOnly = true— no external network exposure - Session correlation uses
UUID(fromcrow.session.idresource attribute), preventing cross-session data leakage - SQLite writes use parameterized queries (
sqlite3_bind_*) throughoutinsertMetric,insertEvent,registerSessionMapping,sumMetric,sumMetricWithAttribute, andcountEvents— no SQL injection risk on the write/read paths - Telemetry is opt-in (disabled by default), respecting user privacy
- Input validation: only
POSTrequests are accepted; unknown paths return 404; malformed payloads return 400
Concerns:
TelemetryDatabase.swift:215-217—deleteSessionDatauses string interpolation ('\(sid)') instead of parameterized queries. Whilesidcomes fromUUID.uuidString(which is always safe hex+hyphens), this breaks the otherwise consistent pattern of parameterized queries. Consider usingsqlite3_bind_textfor consistency and defense-in-depth.
Code Quality
Well done:
- Clean separation of concerns:
OTLPReceiver→TelemetryDatabase→TelemetryService→AppDelegatewiring TelemetryDatabaseis an actor, providing safe concurrent access- WAL mode enabled for SQLite — good choice for concurrent reads during UI updates
- OTLP model types are comprehensive and correctly handle OTLP's quirks (int64-as-string encoding)
- HTTP parser has sensible bounds checking (8KB header limit)
- Session cleanup wired into
onDeleteSession— no data leak when sessions are removed SessionServiceproperly passes telemetry port for both new sessions (sendhandler) and restored sessions (launchClaude)- Analytics strip UI is conditionally shown only when data exists
Minor observations:
HTTPResponse.badRequestatHTTPParser.swift:38uses string interpolation for the JSON error body ("{\"error\":\"\(message)\"}"). If a decode error message contains quotes or special characters, this could produce malformed JSON. Low risk since these messages are only seen by the local OTLP client, but usingJSONSerializationwould be more robust.- No unit tests are included for the new
CrowTelemetrypackage (test target declared inPackage.swiftbut no test files). Given the SQLite query logic insessionAnalytics()and the HTTP parser, tests would add confidence. - The
onDataReceivedcallback inAppDelegate.swift:267-272creates a new Task per telemetry data event. Under high telemetry throughput, this could queue manyanalytics(for:)DB reads. Not likely a real issue given the local single-client scenario, but worth noting.
Summary Table
| Priority | Issue |
|---|---|
| 🟡 Yellow | deleteSessionData uses string interpolation instead of parameterized SQL — consistency concern (TelemetryDatabase.swift:215-217) |
| 🟡 Yellow | No unit tests for CrowTelemetry package (HTTP parser, DB queries, OTLP model decoding) |
| 🟢 Green | HTTPResponse.badRequest could produce malformed JSON with special characters in error messages (HTTPParser.swift:38) |
| 🟢 Green | Per-event Task creation in onDataReceived callback could be coalesced for efficiency |
Recommendation: Approve — this is a clean, well-architected feature addition. The OTLP receiver is properly locked to localhost, telemetry is opt-in, and the code follows good patterns throughout. The yellow items are worth addressing in a follow-up but are not blocking.
Bounds telemetry database growth with a configurable retention window, defaulting to 6 months. Old metrics and events are pruned on app launch. Presets in Settings: 30 days, 90 days, 6 months, 1 year, Forever. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds session analytics to Crow by running an in-process OTLP HTTP/JSON receiver that ingests telemetry exported by Claude Code. Per-session metrics (cost, tokens, tool usage, active time, lines changed) appear in the session detail header.
Closes #136
What's new
New package:
CrowTelemetrySelf-contained, zero third-party dependencies:
OTLPReceiver—NWListenerHTTP server bound to loopback only, handlesPOST /v1/metricsandPOST /v1/logsHTTPParser— Minimal HTTP/1.1 parser (partial-read buffering, Content-Length handling)OTLPModels— Codable types for OTLP HTTP/JSON payloadsTelemetryDatabase— SQLite actor withmetrics,events, andsession_maptables (WAL mode)TelemetryService— Public facade coordinating receiver + databaseNew dependencies
Only system frameworks — no SPM packages added:
import SQLite3) — first SQLite usage in the project. Telemetry DB at~/Library/Application Support/crow/telemetry.db.NWListener) — first inbound-HTTP usage in Crow (previously only Unix domain sockets viaCrowIPC).Integration
SessionService.launchClaude()for restored sessions and thesendRPC handler for new sessions created via/crow-workspace) prepend OTEL env vars to theclaudecommand.OTEL_RESOURCE_ATTRIBUTES=crow.session.id={UUID}reliably correlates OTLP data with Crow sessions.SessionAnalyticsStriprenders as Row 4 inSessionDetailViewwhen data exists (cost, tokens, tools, active time, lines, errors).onDeleteSessionpurges all telemetry for that session.Settings (Settings → General → Telemetry)
Architecture decisions
NWListenerwithacceptLocalOnly=true) — no firewall exposureCrowCoresoCrowUIdoesn't need to depend onCrowTelemetry@Observablescoping viaSessionHookState.analytics— only the visible session re-rendersCommits in this PR
2cbd13c— Add session analytics via Claude Code OpenTelemetry (Feature: Session Analytics & Metrics via Claude Code OpenTelemetry #136)766ba88— Fix OTEL env var injection for both new and restored sessions12a41a9— Default telemetry to disabled (opt-in)3c5ed20— Add configurable telemetry retention windowTest plan
make build— clean compileenv \| grep OTELin session terminal)crow send) and restored sessionsRelease considerations
config.jsonfiles decode correctly (new fields fall back to defaults)telemetry.dbcreated only when feature is enabled🤖 Generated with Claude Code