Skip to content

feat(llma): Add Claude Code LLM Analytics integration#24

Merged
andrewm4894 merged 14 commits intomainfrom
feat/cc-llm-analytics
Apr 13, 2026
Merged

feat(llma): Add Claude Code LLM Analytics integration#24
andrewm4894 merged 14 commits intomainfrom
feat/cc-llm-analytics

Conversation

@andrewm4894
Copy link
Copy Markdown
Member

@andrewm4894 andrewm4894 commented Apr 12, 2026

Summary

  • Adds a SessionEnd hook that parses Claude Code session JSONL logs and sends $ai_generation, $ai_span, and $ai_trace events to PostHog LLM Analytics
  • Requires explicit opt-in via POSTHOG_LLMA_CC_ENABLED=true plus POSTHOG_API_KEY -- existing users are unaffected
  • Costs and $ai_tools_called are calculated by PostHog's ingestion pipeline automatically
  • Supports session-level (default) or per-message trace grouping via POSTHOG_LLMA_TRACE_GROUPING
  • Supports custom properties on all events via POSTHOG_LLMA_CUSTOM_PROPERTIES (JSON object)
  • Zero dependencies -- Python stdlib only

Package structure

posthog_llma/              # reusable Python package
  events.py                # $ai_* event builders
  sender.py                # batch sender + status file
  config.py                # env var / config file loading
  parser.py                # Claude Code JSONL session parser
  event_builder.py         # parsed session -> PostHog events
  trace_naming.py          # trace name selection from prompts
hooks/
  hooks.json               # SessionEnd hook registration
  session-end-llma.py      # thin entry point
scripts/
  llma_cc_ingest.py        # manual session ingestion CLI
commands/
  llma-cc-setup.md         # /posthog:llma-cc-setup
  llma-cc-status.md        # /posthog:llma-cc-status
  llma-cc-ingest.md        # /posthog:llma-cc-ingest
tests/
  test_posthog_llma.py     # event builder tests
  test_session_parser.py   # parser + integration tests

User experience

# Already doing this
claude plugin install posthog

# Enable LLM analytics -- requires explicit opt-in
export POSTHOG_LLMA_CC_ENABLED=true
export POSTHOG_API_KEY="phc_..."
export POSTHOG_HOST="https://eu.i.posthog.com"  # optional, defaults to US

# Optional: tag all events with custom properties
export POSTHOG_LLMA_CUSTOM_PROPERTIES='{"ai_product": "my-app", "team": "platform"}'

Bug fixes

  • Python 3.9 compat: replaced PEP 604 X | None with Optional[X] so entrypoints work on stock macOS python3 (3.9.6) instead of requiring 3.10+
  • $ai_parent_id on generations: $ai_generation events now set $ai_parent_id linking them to their trace root in PostHog's span hierarchy
  • Fallback trace roots: in message grouping mode, generations with unresolved prompt_id now get a root $ai_trace event instead of being orphaned

Test plan

  • Tested end-to-end: session JSONL parsed, events sent to PostHog dev instance
  • Verified $ai_generation events with model, tokens, cost (auto-enriched by PostHog)
  • Verified $ai_generation events include $ai_parent_id pointing at trace root
  • Verified $ai_span events for tool calls with parent-child linking
  • Verified $ai_trace events with session-level grouping and trace name from first meaningful prompt
  • Verified fallback trace IDs get root $ai_trace events in message grouping mode
  • Verified $ai_tools_called auto-extracted by PostHog from output_choices tool_use blocks
  • Verified timestamps are real (from session log) and correctly ordered
  • Verified no-op when POSTHOG_LLMA_CC_ENABLED is not set to true
  • Verified privacy mode redacts prompt/output content
  • Verified custom properties appear on all event types ($ai_generation, $ai_span, $ai_trace)
  • Verified dedup handles streaming duplicate entries in session logs
  • Verified manual ingestion via /posthog:llma-cc-ingest
  • Verified imports succeed on /usr/bin/python3 (macOS 3.9.6)
  • 51 pytest tests passing

Send Claude Code sessions to PostHog LLM Analytics as $ai_generation,
$ai_span, and $ai_trace events. Parses session JSONL on SessionEnd hook
and posts to PostHog's /batch endpoint. No-op unless POSTHOG_API_KEY is
set. Costs are calculated by PostHog's ingestion pipeline automatically.

Supports session-level or per-message trace grouping via
POSTHOG_LLMA_TRACE_GROUPING env var (defaults to session).
@andrewm4894 andrewm4894 self-assigned this Apr 12, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 41d8082d9e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

- Fix tool result matching (check content[].tool_use_id not just sourceToolUseID)
- Fix dedup to keep last entry per message ID (later entries have tool_use blocks)
- Use real timestamps from session log instead of send time
- Add $ai_trace_name from first user prompt (with XML tag stripping)
- Capture slash command invocations that lack promptId
- Add /posthog:llma-cc-ingest command for manual session ingestion
/clear, /exit, /help, [Request interrupted] etc. are skipped in favour
of the first real user prompt.
PostHog's ingestion pipeline extracts $ai_tools_called from
$ai_output_choices content blocks. Include tool_use blocks in
Anthropic format so tools are detected automatically.
@andrewm4894 andrewm4894 changed the title Add Claude Code LLM Analytics integration feat(llma): Add Claude Code LLM Analytics integration Apr 12, 2026
Users may have POSTHOG_API_KEY set for their app's analytics.
Sending Claude Code sessions there silently would be a surprise.
Now requires both POSTHOG_LLMA_CC_ENABLED=true and POSTHOG_API_KEY.
45 tests covering: generation/span/trace building, stop reason mapping,
privacy mode, timestamps, dedup, tool result matching, prompt ID
resolution, trace naming, trace grouping modes, and config loading.
Split monolithic scripts into a proper package:
- posthog_llma/events.py — $ai_* event builders
- posthog_llma/sender.py — batch sender and status file
- posthog_llma/config.py — env var and config file loading
- posthog_llma/parser.py — Claude Code JSONL session parser
- posthog_llma/event_builder.py — parsed session to PostHog events
- posthog_llma/trace_naming.py — trace name selection logic

Hooks and scripts are now thin entry points that import from the
package. All 45 tests passing.
Drop the custom .local.md config file in favour of standard Claude
Code settings.json env blocks. Users can set env vars globally in
~/.claude/settings.json or per-project in .claude/settings.local.json.
When trace_grouping=message and prompt_id is empty, use the
generation's span_id as fallback so its child tool spans share
the same trace_id instead of each getting independent random UUIDs.
…k trace roots

- Replace PEP 604 `X | None` with `Optional[X]` so entrypoints work on
  stock macOS python3 (3.9.6) instead of requiring 3.10+
- Add $ai_parent_id to $ai_generation events so they link to their trace
  root in PostHog's span hierarchy
- Emit $ai_trace root events for fallback (unresolved prompt) trace IDs
  in message grouping mode, closing the orphan gap
Users can set a JSON object of custom properties that get merged into
every $ai_generation, $ai_span, and $ai_trace event. Useful for
tagging events with team, product, or environment metadata for
filtering in PostHog.
- Wrap hook main() in top-level try/except so any unexpected error
  silently exits 0 instead of showing users a traceback
- Use safe int parsing for max_attribute_length config to handle
  invalid env var values gracefully
Tool use blocks in $ai_output_choices were only sending type and name,
missing the input args PostHog displays in the trace UI. Now includes
args with safe truncation to max_attribute_length and a fallback for
unserializable inputs.
@andrewm4894 andrewm4894 merged commit 3ae88bc into main Apr 13, 2026
10 checks passed
@andrewm4894 andrewm4894 deleted the feat/cc-llm-analytics branch April 13, 2026 13:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant