You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We cannot answer what a feature costs to build. Providers expose tokens, model and cost per day and per person, but nothing that ties a spend to the work it produced: no commit, no pull request, no ticket. They also expose nothing about how the work ran — effort, speed mode, agent, skills invoked — which is exactly the lever a team can act on.
Two gaps, one cause: the identifier that would join a session to an artefact is known by the runtime and never written anywhere durable.
A skill cannot close this gap. A skill is an instruction to the model: it has to remember to stamp the commit, and it does not reliably know its own session_id. Best-effort stamping fails on long, loaded sessions — precisely the expensive ones we want to measure. This has to be runtime plumbing, installed and verified by the CLI, not prompt discipline.
Scope
Stamping — two halves, because neither can do it alone. The host hook knows the session id but cannot write the commit message; the git hook owns the message but knows nothing about the session.
Host hook PreToolUse on Bash (new plugins/aidd-vcs/hooks/hooks.json): when the command is a git commit, write a one-shot marker under $(git rev-parse --absolute-git-dir)/aidd/, containing the session_id from the event payload.
Git hook prepare-commit-msg: consume the marker (read, then delete) and append the trailer with git interpret-trailers.
AIDD-Session-Id: <session_id>
Consuming the marker is the guarantee: one marker, one commit. A commit typed by a human in another terminal while a session is open is never stamped, so cost is never over-attributed.
Why not SessionStart. Simpler to wire, but it stamps every commit in the repo for as long as the session is open, including the user's own. Rejected for KPI use.
CLI ownership. The pipeline is installed, upgraded and verified by the CLI, never hand-wired:
Install writes the host hook entry per supported tool and the git hook.
Git hook installation must detect the host project's hook manager — core.hooksPath, lefthook, husky, or none — and append to it, never overwrite a sibling. Writing .git/hooks/prepare-commit-msg on a repo that sets core.hooksPath produces a hook git never runs, and a silent hole in the data.
Status. Extend the existing status / doctor surface rather than add a parallel command. It answers, per scope: telemetry enabled or not, host hook wired per tool, git hook installed and reachable by this repo's manager, exporter configured, and when the last stamped commit happened. A configuration that looks installed but produces nothing must read as broken, not healthy.
Skills keep a narrow role, for what the model genuinely knows and the runtime does not:
aidd-vcs/01-commit: AIDD-Task-Id trailer, linking the commit to a declared task.
aidd-vcs/02-pull-request: recap the session ids in the PR body. Deliberate redundancy — a squash-merge can drop individual commit messages, and that is exactly when the pull request becomes the unit of cost.
Tool coverage. Claude Code, Codex, Cursor, Copilot follow 08-hook-generate/references/tool-paths.md. OpenCode needs a JS plugin module, not a config entry: out of v1, stated as such by status.
Non-negotiable: fail open. A telemetry hook that errors, times out, or finds no marker exits 0 and stays silent. It must never block, delay or alter a commit. No prompt content, no file content, no diff leaves the machine; the trailer carries an opaque id and nothing else.
Out of scope
Exporting to a backend, dashboards, cost computation.
Effort, speed mode, agent type and skill-invocation events. Same collector, next issue — this one only opens the attribution path.
Rewriting history to stamp past commits.
Acceptance criteria
A commit created by an agent in an enabled repo carries AIDD-Session-Id, on Claude Code and at least one other supported tool.
A commit created by a human while a session is open carries no trailer.
The stamped id matches the session.id seen in the provider's OpenTelemetry export (verified on one real session, both values recorded in the issue).
Installing on a repo using core.hooksPath, on one using lefthook, and on one using neither, leaves every pre-existing hook working.
A failing, missing or slow hook never blocks a commit; the commit succeeds unstamped.
status reports enabled / wired / reachable / last stamped commit, and reports a hook that git will never run as broken.
Disabling removes the entries it added and nothing else.
git log --format='%(trailers:key=AIDD-Session-Id,valueonly)' returns the ids for stamped commits.
Open questions
Do sub-agents propagate the parent session_id to PreToolUse? If a commit fires from a sub-agent with its own id, the parent-child edge must be captured too, or part of the cost detaches from the feature.
Trailer name: AIDD-Session-Id vs a vendor-neutral X-AI-Session-Id. It lands in the history of public repositories permanently.
Public repositories: off by default. Explicit per-repo opt-in. A contributor's identifier must never enter a permanent public history without a decision.
Status is a checklist, one line per independently verifiable claim:
aidd telemetry status
ok enabled for this repository
ok hooks wired claude-code, codex
ok git hook reachable .githooks/prepare-commit-msg (core.hooksPath)
ok commits stamped 18/20 over 7 days
FAIL cost join session.id absent from export (OTEL_METRICS_INCLUDE_SESSION_ID=false)
The last line is the point of the command: an installation can be complete and still join nothing.
Acceptance criteria, added by the decisions
status prints one line per check, each independently true or false, never a single aggregate verdict.
An installed-but-inert state reads as failed: Codex hook trust pending or [features] hooks = false, Copilot disableAllHooks, Claude Code OTEL_METRICS_INCLUDE_SESSION_ID=false.
The share of stamped commits over the last 7 days is one of the lines, because it is the only check that proves the pipeline produces data rather than merely exists.
On a public repository, status reports telemetry as disabled until the repo opts in.
Problem
We cannot answer what a feature costs to build. Providers expose tokens, model and cost per day and per person, but nothing that ties a spend to the work it produced: no commit, no pull request, no ticket. They also expose nothing about how the work ran — effort, speed mode, agent, skills invoked — which is exactly the lever a team can act on.
Two gaps, one cause: the identifier that would join a session to an artefact is known by the runtime and never written anywhere durable.
A skill cannot close this gap. A skill is an instruction to the model: it has to remember to stamp the commit, and it does not reliably know its own
session_id. Best-effort stamping fails on long, loaded sessions — precisely the expensive ones we want to measure. This has to be runtime plumbing, installed and verified by the CLI, not prompt discipline.Scope
Stamping — two halves, because neither can do it alone. The host hook knows the session id but cannot write the commit message; the git hook owns the message but knows nothing about the session.
PreToolUseonBash(newplugins/aidd-vcs/hooks/hooks.json): when the command is agit commit, write a one-shot marker under$(git rev-parse --absolute-git-dir)/aidd/, containing thesession_idfrom the event payload.prepare-commit-msg: consume the marker (read, then delete) and append the trailer withgit interpret-trailers.Consuming the marker is the guarantee: one marker, one commit. A commit typed by a human in another terminal while a session is open is never stamped, so cost is never over-attributed.
Why not
SessionStart. Simpler to wire, but it stamps every commit in the repo for as long as the session is open, including the user's own. Rejected for KPI use.CLI ownership. The pipeline is installed, upgraded and verified by the CLI, never hand-wired:
telemetryblock in.aidd/config.yml(feat(framework): .aidd/ project config root (YAML policy, multi-tool) #585): opt-in flag, trailer name, optional OTLP endpoint. No secrets in the committed file.core.hooksPath, lefthook, husky, or none — and append to it, never overwrite a sibling. Writing.git/hooks/prepare-commit-msgon a repo that setscore.hooksPathproduces a hook git never runs, and a silent hole in the data.Status. Extend the existing
status/doctorsurface rather than add a parallel command. It answers, per scope: telemetry enabled or not, host hook wired per tool, git hook installed and reachable by this repo's manager, exporter configured, and when the last stamped commit happened. A configuration that looks installed but produces nothing must read as broken, not healthy.Skills keep a narrow role, for what the model genuinely knows and the runtime does not:
aidd-vcs/01-commit:AIDD-Task-Idtrailer, linking the commit to a declared task.aidd-vcs/02-pull-request: recap the session ids in the PR body. Deliberate redundancy — a squash-merge can drop individual commit messages, and that is exactly when the pull request becomes the unit of cost.Tool coverage. Claude Code, Codex, Cursor, Copilot follow
08-hook-generate/references/tool-paths.md. OpenCode needs a JS plugin module, not a config entry: out of v1, stated as such bystatus.Non-negotiable: fail open. A telemetry hook that errors, times out, or finds no marker exits
0and stays silent. It must never block, delay or alter a commit. No prompt content, no file content, no diff leaves the machine; the trailer carries an opaque id and nothing else.Out of scope
Acceptance criteria
AIDD-Session-Id, on Claude Code and at least one other supported tool.session.idseen in the provider's OpenTelemetry export (verified on one real session, both values recorded in the issue).core.hooksPath, on one using lefthook, and on one using neither, leaves every pre-existing hook working.statusreports enabled / wired / reachable / last stamped commit, and reports a hook that git will never run as broken.git log --format='%(trailers:key=AIDD-Session-Id,valueonly)'returns the ids for stamped commits.Open questions
session_idtoPreToolUse? If a commit fires from a sub-agent with its own id, the parent-child edge must be captured too, or part of the cost detaches from the feature.AIDD-Session-Idvs a vendor-neutralX-AI-Session-Id. It lands in the history of public repositories permanently.Prior art in this repo
plugins/aidd-context/hooks/hooks.json— proven plugin-hook pattern (SessionStart+ node script).plugins/aidd-context/skills/08-hook-generate/references/tool-paths.md— per-tool events, scopes, handler contract, safety checks.plugins/aidd-vcs/skills/01-commit/actions/03-commit.md— where the task trailer belongs.cli/src/application/commands/status.ts,doctor.ts— the drift and health surfaces to extend..aidd/config.ymlhuman policy root, the home for thetelemetryblock.Relations
Decisions (2026-08-12)
AIDD-Session-Id. It carries the AIDDrun_id, not a vendor identifier. The vendor id and its kind live in the session record (feat(framework): per-session ledger in the task folder, keyed by an AIDD run id #620), because no vendor guarantees its hook id matches its telemetry id.The last line is the point of the command: an installation can be complete and still join nothing.
Acceptance criteria, added by the decisions
statusprints one line per check, each independently true or false, never a single aggregate verdict.[features] hooks = false, CopilotdisableAllHooks, Claude CodeOTEL_METRICS_INCLUDE_SESSION_ID=false.statusreports telemetry as disabled until the repo opts in.