Skip to content

spike(framework): is a hook's session id the one telemetry exports #632

Description

@blafourcade

Question

Is the session identifier a hook receives the same value the tool's telemetry export carries?

Decision

It unblocks the whole layer. If the two differ, no task can be attached to its cost by that route, and the design has to fall back on injecting an AIDD identifier before the process starts — which needs a launcher the CLI does not have.

Bounds

  • Evidence needed: one real session per tool, the identifier seen by the hook and the one in the export recorded side by side.
  • Stop when: the value is compared on at least three tools, or a tool makes the comparison impossible for a documented reason.

Investigation

Run 13-14 August 2026. Bench: a local OTLP collector, a hook that records the payload it receives, one throwaway session per tool in an isolated directory.

Result: the values are identical, on three tools, with a fourth confirmed on the export side only.

Tool Hook field Export attribute Cost of the test
Claude Code session_id session.id two real sessions
Codex CLI session_id conversation.id on codex.sse_event zero tokens
GitHub Copilot sessionId gen_ai.conversation.id on invoke_agent zero credits
OpenCode not tested plugin-side session.id on ai.streamText one session
Cursor conversation_id not measurable export is an Enterprise team setting

A reusable method

Identifiers are minted client-side, before any model call. A provider pointed at an address that answers nothing still opens the session, fires the hook and emits telemetry. Codex and Copilot verify for free. Cursor is the exception: it validates the key, then the model, then workspace trust before opening a session, so checking it costs a real turn.

The check therefore belongs in continuous integration rather than in an end-of-project checkbox. The equality is proven by a session, not by construction, and a tool update can break it without notice.

What the investigation found in passing, and which matters more

No probe worked on the first attempt, and never for a different reason. Writing the hook file is not enough; a gate has to be lifted, and every tool gates differently.

Tool Gate Lifted by
Codex feature flag and persisted hook trust --enable hooks + --dangerously-bypass-hook-trust
Copilot folder trust on .github/hooks/ user scope under $COPILOT_HOME/hooks/
Cursor workspace trust --trust
Claude Code none —

The finding that reshaped the design

Measured on a real session with the AIDD plugins installed from their marketplace:

Carrier default with OTEL_LOG_TOOL_DETAILS=1
metric claude_code.token.usage skill.name = third-party skill.name = third-party
metric claude_code.cost.usage skill.name = third-party skill.name = third-party
event skill_activated skill.name = custom_skill skill.name = aidd-context:11-explore

The documentation says it for anyone reading to the end: third-party plugin skill names are replaced. AIDD ships from a third-party marketplace, so every AIDD skill collapses into one label on the metrics, and the un-redaction flag does not separate them — it only affects events.

Three consequences. Per-step cost must be correlated from log events, not filtered from a metric attribute, so the pipeline ingests events from v1. Claude Code stops being the metric-grain exception; that holds for per-session totals only. And the same flag also logs Bash commands and tool inputs, so collector-side redaction becomes a privacy requirement rather than a convenience.

Two measured limits to carry into the design

  • skill.name is sticky. Once a skill is activated the following datapoints carry it, including those of subagents launched afterwards. Correct for steps that follow one another, wrong for interleaved skills.
  • Subagents differ per tool. On Claude Code they share the parent identifier and are told apart by query_source; Cursor documents the opposite.

A method error, recorded

A first conclusion that OpenCode exported nothing was wrong twice: the switch is a config key (experimental.openTelemetry), not an environment variable, and the run that seemed to confirm the absence was void — the collector had failed to bind its port. The probe's reporter now checks the collector is listening before drawing any conclusion.

Full detail and sources: aidd_docs/brainstorm/2026_08_13-telemetry-layer.md

Activity

  1. moved this from Ideation to Done in AIDD Roadmapon Aug 14, 2026
  2. closed this as completedby moving to Done in AIDD Roadmapon Aug 14, 2026
  3. changed the title [-]spike(framework): l'identifiant d'un hook est-il celui de la télémétrie[/-] [+]spike(framework): is a hook's session id the one telemetry exports[/+] on Aug 14, 2026
  4. blafourcade commented on Sep 11, 2026

    @blafourcade
    ContributorAuthor

    The original result remains the evidence for session identity. The active route changed: telemetry no longer depends on an OTLP collector or export.

    scripts/probe-identifier-join.cjs now verifies the join that production uses: hook → journal → Claude transcript → aidd telemetry read. It runs on every pull request with zero token cost and distinguishes a changed tool from a broken probe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Fields

    Priority

    High

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions