Skip to content

Capture Claude Code sessions when SessionEnd doesn't fire (orchestrated / sandboxed runs)Β #77

@nkovacic

Description

@nkovacic

Capture Claude Code sessions when SessionEnd doesn't fire (orchestrated / sandboxed runs)

Hi πŸ‘‹ β€” thanks for the plugin, LLMA capture has been really useful and the code is a pleasure to read through.

We're running Claude Code inside ephemeral Daytona sandboxes via sandbox-agent, which launches Claude through the claude-agent-acp adapter. In that setup the LLMA SessionEnd hook never fires β€” the sandbox is destroyed when the task completes (effectively SIGKILL), and the documented SessionEnd matchers (clear | resume | logout | prompt_input_exit | bypass_permissions_disabled | other, per the hooks docs) don't obviously cover an ACP session closing either. Result: zero capture, silently β€” session-end-llma.py does sys.exit(0) on any error / early return (a very reasonable "never break the user's workflow" choice!), so it's easy to think it's working when it isn't.

We confirmed the sender itself is fine: running the same JSONL through ${CLAUDE_PLUGIN_ROOT}/hooks/session-end-llma.py manually (piping {"session_id": ..., "cwd": ...} on stdin) cleanly delivers full capture β€” ~160 events in our case, generations + tool spans + traces, parser handles the still-growing JSONL gracefully. So the data is on disk and the path works; there just isn't an orchestrator-friendly way to trigger it.

Proposal: an opt-in Stop-hook flush mode

What would unblock us β€” and feels like a good default for autonomous / orchestrated agent setups generally β€” is an optional incremental-flush mode gated by an env var:

POSTHOG_LLMA_FLUSH_ON=session_end   # current behavior, default
POSTHOG_LLMA_FLUSH_ON=stop          # flush on every Stop hook (per turn)

This pairs naturally with POSTHOG_LLMA_TRACE_GROUPING=message: each user prompt β†’ one trace β†’ one flush boundary, which gives a clean de-dup story (only emit events whose prompt_id matches the just-finished turn). The orchestrator survives the host killing the process between turns without losing anything.

A doc-only alternative would also help our specific case β€” just blessing "pipe {session_id, cwd} into hooks/session-end-llma.py from your orchestrator" in the README β€” though it leaves the silent-failure mode in place for everyone else.

Happy to send a PR; mostly wanted to surface the case and hear how you'd prefer to shape it.

Environment

  • Plugin posthog v1.1.21 via claude-plugins-official (source PostHog/ai-plugin@ff08c376)
  • Claude Code on macOS for local testing; Daytona sandbox via claude-agent-acp for the orchestrated path
  • PostHog Cloud, EU region

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions