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
{{ message }}
Repository navigation
fix(framework): the run journal never writes on Copilot #681
Copilot accepts PascalCase as an alias but rewrites it to its canonical camelCase name and stamps the entry _vsCodeCompat (app.js, tpe()).
_vsCodeCompat selects a different payload builder. The compat base is { hook_event_name, session_id, timestamp, cwd }, snake_case; the canonical base is { sessionId, timestamp, cwd }, camelCase.
plugins/aidd-telemetry/hooks/lib/host.js detects Copilot as "has sessionId AND has no hook_event_name". A compat payload has neither property: it has session_id, and it does have hook_event_name.
No other branch matches (transcript_path is absent), so detectHost() returns null and processPayload returns before touching the journal.
Bounds, stated plainly
Established by reading @github/[email protected]'s bundle. The runtime on the test machine reports 1.0.80, and its binary is packed and not readable. This is not confirmed against a real payload.
What settles it: install the framework's .github/hooks/*.hooks.json in a repo, run a Copilot session, dump the hook's actual stdin, and read which key set arrives.
Done when
A real Copilot hook payload is captured and the key set is recorded in a fixture.
detectHost() recognises whichever shape actually arrives, and a test fails if it stops doing so.
If both shapes can arrive, both are recognised rather than one being assumed.
Also worth knowing
Copilot defers project-scope hooks past session creation. In non-interactive prompt mode the deferred load runs only when the folder is trusted or COPILOT_ALLOW_ALL=true / GITHUB_COPILOT_PROMPT_MODE_REPO_HOOKS=true is set. Whether a project-scope SessionStart hook still fires after a deferred load is not established, and the journal's first line depends on it.
sessionId, no hook_event_name - exactly what detectHost() looks for. So the canonical path works today.
The framework declares PascalCase, which Copilot accepts as an alias but rewrites while tagging the entry _vsCodeCompat, switching it to the snake_case payload. That path is still uncaptured, so the defect stands unconfirmed - but the fix is probably one declaration rather than a change to host detection: emit the canonical camelCase names for Copilot and the existing detection holds.
Capture the PascalCase-declared payload before choosing between the two.
The reasoning in this ticket was read out of a 1.0.57 bundle and had never met a payload. It has now. A dumping hook was registered as an additional file in ~/.copilot/hooks/ — nothing else in that directory was touched — and one session was run.
The compat shape, exactly as reasoned: session_id in snake_case, and hook_event_name present. detectHost recognises Copilot as has sessionId and has no hook_event_name, so it answers null and processPayload returns before the journal is touched.
Every link in the chain above holds. The fix is the one this ticket describes.
Two things the capture adds.
SessionStart also carries initial_prompt — the user's own text. Nothing here reads it and nothing should, but a detector that starts logging payloads for diagnosis would be logging prompts.
Stop carries a transcript_path, which the earlier reading did not predict:
That matters twice. It is a second payload shape carrying transcript_path, so whatever detectHost does must not confuse it with Claude Code (/projects/….jsonl) or Codex (/sessions/YYYY/MM/DD/rollout-…); it matches neither today, and a test should say so rather than leaving it to luck. And it points at a file this repository has never read — see the finding filed separately.
Still open: whether a project-scope SessionStart fires when hooks are deferred past session creation. This capture used a user-scope hook and COPILOT_ALLOW_ALL=true, so it does not answer it.
Captured, and the chain read from source was right
@github/[email protected], one real session, the framework's own installed plugin. All three hooks fired — no deferral, no trust prompt, nothing to lift:
That is the _vsCodeCompat shape this ticket predicted, down to the spelling: snake_case session_id, hook_event_name present and carrying Claude Code's own event names, no sessionId anywhere. Against the old detectHost() all three returned null, so the run journal wrote nothing — confirmed by replaying the untouched captures through journal.js's real stdin path.
One fact the ticket did not have: both shapes are real. The canonical camelCase payload from the #663 probes is also a genuine capture, so recognition was added rather than swapped. The ticket's third Done-when — "if both shapes can arrive, both are recognised" — is the case that actually applies.
What tells them apart
timestamp, together with hook_event_name and session_id. No other host's payload has ever carried timestamp, across every fixture in the repository, and the branch sits after the two transcript_path patterns so a host that does carry one is claimed by its own shape first. Verified across all seventeen fixtures: every one still detects as the host it came from, Copilot's Stop event included, and that one does carry a transcript_path.
record.js now reads payload.sessionId ?? payload.session_id for Copilot — both spellings are Copilot's own, neither is a fallback guess.
Left alone, deliberately
docs/telemetry-limits.md is untouched. The captured session used a Bash tool only, so nothing in it bears on what the doc says about per-step breakdown. Editing it on this evidence would be the guess this milestone exists to prevent.
The gap this opens
lib/step-starts.js reads Copilot's skill invocation as toolName / toolArgs — the canonical spelling. A compat PostToolUse carries tool_name / tool_input, so on that shape no step line opens: the session journals, and every record in it reads unattributed. Filed separately, because closing it needs a capture this one does not provide — a Copilot session that actually invokes a skill, to learn what the compat builder calls that tool and where it puts the skill name. Guessing those two field values would be the same mistake this ticket was opened to correct.
Gate
node --test scripts/__tests__/aidd-telemetry-journal.test.js — 159 pass, 0 fail, 13 of them new: fixture-based recognition of both shapes, a guard proving the compat rule does not over-claim a Codex or Claude Code payload, the three untouched captures replayed end to end, and a pair showing a recognised payload writes a run file where one missing only timestamp writes none.
Done and awaiting review in #706, which closes this on merge.
The work is on claude/aidd-telemetry-layer-e403uf, eleven commits, targeting next. Gate at the time of push: 365 plugin specs, 1,931 CLI unit, 577 integration, 178 e2e, tsc and biome clean, no broken markdown links.
Read the eleven commits rather than the pull request's file count — the branch carries its own copy of the CLI migration that next has since received, so the diff counts it twice. The range and the real figures are in the first comment on #706: 142 files, +10,518 −418.
Every claim about a tool in this work rests on a session that was actually run. The negative results are kept in full in aidd_docs/tasks/2026_08/2026_08_22_telemetry-every-tool/measurements.md, including the two probes that disagreed about OpenCode and the measurement that reconciled them.
Activity
blafourcade commentedon Aug 20, 2026
Narrowing this, from a captured payload rather than from source.
A real Copilot hook payload, from a probe whose hooks file declared canonical camelCase event names:
{"sessionId": "b349a8c0-…", "timestamp": 1787047151787, "cwd": "…", "toolName": "skill", "toolArgs": "{\"skill\":\"probe-echo\"}"}sessionId, nohook_event_name- exactly whatdetectHost()looks for. So the canonical path works today.The framework declares PascalCase, which Copilot accepts as an alias but rewrites while tagging the entry
_vsCodeCompat, switching it to the snake_case payload. That path is still uncaptured, so the defect stands unconfirmed - but the fix is probably one declaration rather than a change to host detection: emit the canonical camelCase names for Copilot and the existing detection holds.Capture the PascalCase-declared payload before choosing between the two.
blafourcade commentedon Aug 21, 2026
Confirmed against a real payload, on 1.0.80
The reasoning in this ticket was read out of a
1.0.57bundle and had never met a payload. It has now. A dumping hook was registered as an additional file in~/.copilot/hooks/— nothing else in that directory was touched — and one session was run.SessionStart, as it actually arrives:{ "hook_event_name": "SessionStart", "session_id": "9410fe62-…", "timestamp": "2026-08-21T14:07:46.858Z", "cwd": "/private/tmp/cop", "source": "new" }The compat shape, exactly as reasoned:
session_idin snake_case, andhook_event_namepresent.detectHostrecognises Copilot as hassessionIdand has nohook_event_name, so it answersnullandprocessPayloadreturns before the journal is touched.Every link in the chain above holds. The fix is the one this ticket describes.
Two things the capture adds.
SessionStartalso carriesinitial_prompt— the user's own text. Nothing here reads it and nothing should, but a detector that starts logging payloads for diagnosis would be logging prompts.Stopcarries atranscript_path, which the earlier reading did not predict:{ "hook_event_name": "Stop", "session_id": "9410fe62-…", "transcript_path": "~/.copilot/session-state/9410fe62-…/events.jsonl", "stop_reason": "end_turn" }That matters twice. It is a second payload shape carrying
transcript_path, so whateverdetectHostdoes must not confuse it with Claude Code (/projects/….jsonl) or Codex (/sessions/YYYY/MM/DD/rollout-…); it matches neither today, and a test should say so rather than leaving it to luck. And it points at a file this repository has never read — see the finding filed separately.Still open: whether a project-scope
SessionStartfires when hooks are deferred past session creation. This capture used a user-scope hook andCOPILOT_ALLOW_ALL=true, so it does not answer it.blafourcade commentedon Aug 21, 2026
Captured, and the chain read from source was right
@github/[email protected], one real session, the framework's own installed plugin. All three hooks fired — no deferral, no trust prompt, nothing to lift:That is the
_vsCodeCompatshape this ticket predicted, down to the spelling: snake_casesession_id,hook_event_namepresent and carrying Claude Code's own event names, nosessionIdanywhere. Against the olddetectHost()all three returnednull, so the run journal wrote nothing — confirmed by replaying the untouched captures throughjournal.js's real stdin path.One fact the ticket did not have: both shapes are real. The canonical camelCase payload from the #663 probes is also a genuine capture, so recognition was added rather than swapped. The ticket's third Done-when — "if both shapes can arrive, both are recognised" — is the case that actually applies.
What tells them apart
timestamp, together withhook_event_nameandsession_id. No other host's payload has ever carriedtimestamp, across every fixture in the repository, and the branch sits after the twotranscript_pathpatterns so a host that does carry one is claimed by its own shape first. Verified across all seventeen fixtures: every one still detects as the host it came from, Copilot's Stop event included, and that one does carry atranscript_path.record.jsnow readspayload.sessionId ?? payload.session_idfor Copilot — both spellings are Copilot's own, neither is a fallback guess.Left alone, deliberately
docs/telemetry-limits.mdis untouched. The captured session used a Bash tool only, so nothing in it bears on what the doc says about per-step breakdown. Editing it on this evidence would be the guess this milestone exists to prevent.The gap this opens
lib/step-starts.jsreads Copilot's skill invocation astoolName/toolArgs— the canonical spelling. A compatPostToolUsecarriestool_name/tool_input, so on that shape no step line opens: the session journals, and every record in it reads unattributed. Filed separately, because closing it needs a capture this one does not provide — a Copilot session that actually invokes a skill, to learn what the compat builder calls that tool and where it puts the skill name. Guessing those two field values would be the same mistake this ticket was opened to correct.Gate
node --test scripts/__tests__/aidd-telemetry-journal.test.js— 159 pass, 0 fail, 13 of them new: fixture-based recognition of both shapes, a guard proving the compat rule does not over-claim a Codex or Claude Code payload, the three untouched captures replayed end to end, and a pair showing a recognised payload writes a run file where one missing onlytimestampwrites none.feat(telemetry): the journal records which step was running, on four …
docs(telemetry): the plan the Copilot journal is built from, and one …
blafourcade commentedon Aug 22, 2026
Done and awaiting review in #706, which closes this on merge.
The work is on
claude/aidd-telemetry-layer-e403uf, eleven commits, targetingnext. Gate at the time of push: 365 plugin specs, 1,931 CLI unit, 577 integration, 178 e2e,tscand biome clean, no broken markdown links.Read the eleven commits rather than the pull request's file count — the branch carries its own copy of the CLI migration that
nexthas since received, so the diff counts it twice. The range and the real figures are in the first comment on #706: 142 files, +10,518 −418.Every claim about a tool in this work rests on a session that was actually run. The negative results are kept in full in
aidd_docs/tasks/2026_08/2026_08_22_telemetry-every-tool/measurements.md, including the two probes that disagreed about OpenCode and the measurement that reconciled them.feat(telemetry): the journal records which step was running, on four …
docs(telemetry): the plan the Copilot journal is built from, and one …
docs(framework): copilot's event log is not a payload capture
test(framework): the session #681 asked for, and what it settled
feat(framework): measurement that works on five tools, each proven by…
blafourcade commentedon Sep 2, 2026
Delivered by #706, squash-merged into
nextas 627408f.