feat(telemetry): missed-opportunity signal at checkpoint condensation - #2024
feat(telemetry): missed-opportunity signal at checkpoint condensation#2024jdx wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds a new, content-free telemetry signal emitted on successful post-commit checkpoint condensation to better measure “missed opportunity” adoption: sessions that committed files with prior AI checkpoint history without using entire search.
Changes:
- Introduces a
cli_checkpoint_condensedtelemetry event payload and detached sender (CheckpointCondensedSignal, builder, tracker). - Adds strategy-side signal computation:
used_search(substring probe over in-memory transcript) andprior_ai_history(boundedgit logscan for recent commits with anEntire-Checkpointtrailer touching committed files). - Wires emission into the post-commit condensation flow and adds unit tests for transcript probing and the git-log-based prior-history detector.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| cmd/entire/cli/telemetry/detached.go | Adds the new condensed-checkpoint telemetry signal type, payload builder, and detached tracking function. |
| cmd/entire/cli/telemetry/detached_test.go | Adds payload-building tests for the new event and centralizes the agent test constant. |
| cmd/entire/cli/strategy/telemetry_signals.go | Implements used_search detection + prior AI-history probing and emits the condensed-checkpoint telemetry signal (telemetry opt-in gated). |
| cmd/entire/cli/strategy/telemetry_signals_test.go | Adds unit tests for transcript search detection and prior-checkpoint-touch detection via a temp git repo. |
| cmd/entire/cli/strategy/manual_commit_types.go | Extends CondenseResult with UsedSearch for downstream telemetry emission. |
| cmd/entire/cli/strategy/manual_commit_hooks.go | Hooks telemetry emission into the successful condensation path. |
| cmd/entire/cli/strategy/manual_commit_condensation.go | Populates CondenseResult.UsedSearch from the extracted raw transcript. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // without searching" denominator that raw command counts cannot. Booleans and | ||
| // counts only — no file paths, prompts, or transcript content. | ||
| type CheckpointCondensedSignal struct { | ||
| // Agent is the session-owning agent type (e.g. "claude-code"). | ||
| Agent string |
| priorAIHistory := false | ||
| if root, rootErr := paths.WorktreeRoot(ctx); rootErr == nil { | ||
| priorAIHistory = priorAICommitTouchedFiles(ctx, root, result.FilesTouched) | ||
| } | ||
| telemetry.TrackCheckpointCondensedDetached(telemetry.CheckpointCondensedSignal{ | ||
| Agent: string(state.AgentType), | ||
| UsedSearch: result.UsedSearch, | ||
| PriorAIHistory: priorAIHistory, | ||
| FilesCommitted: len(result.FilesTouched), | ||
| }, s.Enabled, versioninfo.Version) |
Address Copilot review on #2024: - emitCheckpointCondensedTelemetry resolves state.AgentType (display name, "Claude Code") to the agent registry key ("claude-code") so the agent property lines up with skill and command events; unknown agent types fall back to the stored string rather than dropping the signal. - CheckpointCondensedSignal doc now states the actual invariant (content-free metadata, no file paths/prompts/transcript content) instead of "booleans and counts only", and documents Agent as the registry key. - BuildCheckpointCondensedPayload defaults an empty agent to "auto" like the other payload builders; the shared literal moved to the autoAgentName const. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3d4737d to
6f6eda2
Compare
|
Both findings addressed in 6f6eda2:
Rebased on the updated #2023. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.
Suppressed comments (3)
cmd/entire/cli/strategy/telemetry_signals.go:93
- The environment opt-out is not checked until
TrackCheckpointCondensedDetached, after this function has already run thegit logprobe. A user who opted in previously and then setsENTIRE_TELEMETRY_OPTOUTstill incurs the repository scan, contrary to the PR's guarantee that no work/probe occurs for opted-out users. Include the environment opt-out in this early gate.
s, err := settings.Load(ctx)
if err != nil || s.Telemetry == nil || !*s.Telemetry {
return
cmd/entire/cli/strategy/manual_commit_hooks.go:1411
- This call executes inside the
MutateSessionStatecallback at lines 1004-1010, so settings I/O, agit logsubprocess, machine-ID lookup, and detached-process spawn all extend the per-session gate hold. The established telemetry pattern explicitly requires emission after mutation returns (cmd/entire/cli/lifecycle.go:1579-1581) to avoid blocking concurrent hooks. Capture the signal while locked, then run this telemetry work afterMutateSessionStatecompletes.
// Content-free adoption signal (opt-in telemetry gated inside).
emitCheckpointCondensedTelemetry(ctx, state, result)
cmd/entire/cli/strategy/telemetry_signals.go:40
- The acknowledged quoting behavior makes
prior_ai_historyincorrect for ordinary non-ASCII paths (for example, Git outputs"caf\303\251.go"whileFilesTouchedcontainscafé.go), and line-based parsing also cannot represent filenames containing newlines. This systematically creates false negatives in the metric. Request NUL-delimited names (-z) and parse those names without trimming or Git quote decoding.
// Paths git quotes in --name-only output (e.g. non-ASCII names) won't match
// their unquoted FilesTouched form; that false-negative is acceptable for a
// telemetry boolean.
Address Copilot review on #2024: - emitCheckpointCondensedTelemetry resolves state.AgentType (display name, "Claude Code") to the agent registry key ("claude-code") so the agent property lines up with skill and command events; unknown agent types fall back to the stored string rather than dropping the signal. - CheckpointCondensedSignal doc now states the actual invariant (content-free metadata, no file paths/prompts/transcript content) instead of "booleans and counts only", and documents Agent as the registry key. - BuildCheckpointCondensedPayload defaults an empty agent to "auto" like the other payload builders; the shared literal moved to the autoAgentName const. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6f6eda2 to
d96d143
Compare
Address Copilot review on #2024: - emitCheckpointCondensedTelemetry resolves state.AgentType (display name, "Claude Code") to the agent registry key ("claude-code") so the agent property lines up with skill and command events; unknown agent types fall back to the stored string rather than dropping the signal. - CheckpointCondensedSignal doc now states the actual invariant (content-free metadata, no file paths/prompts/transcript content) instead of "booleans and counts only", and documents Agent as the registry key. - BuildCheckpointCondensedPayload defaults an empty agent to "auto" like the other payload builders; the shared literal moved to the autoAgentName const. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
d96d143 to
5a79a16
Compare
|
Addressed the three remaining review findings in 5a79a16, and rebased onto the updated #2023:
Full suite re-run on the stack: 9,324 unit / 514 integration / canary green. |
Address Copilot review on #2024: - emitCheckpointCondensedTelemetry resolves state.AgentType (display name, "Claude Code") to the agent registry key ("claude-code") so the agent property lines up with skill and command events; unknown agent types fall back to the stored string rather than dropping the signal. - CheckpointCondensedSignal doc now states the actual invariant (content-free metadata, no file paths/prompts/transcript content) instead of "booleans and counts only", and documents Agent as the registry key. - BuildCheckpointCondensedPayload defaults an empty agent to "auto" like the other payload builders; the shared literal moved to the autoAgentName const. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5a79a16 to
7d418f4
Compare
Address Copilot review on #2024: - emitCheckpointCondensedTelemetry resolves state.AgentType (display name, "Claude Code") to the agent registry key ("claude-code") so the agent property lines up with skill and command events; unknown agent types fall back to the stored string rather than dropping the signal. - CheckpointCondensedSignal doc now states the actual invariant (content-free metadata, no file paths/prompts/transcript content) instead of "booleans and counts only", and documents Agent as the registry key. - BuildCheckpointCondensedPayload defaults an empty agent to "auto" like the other payload builders; the shared literal moved to the autoAgentName const. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7d418f4 to
cdd3faf
Compare
Address Copilot review on #2024: - emitCheckpointCondensedTelemetry resolves state.AgentType (display name, "Claude Code") to the agent registry key ("claude-code") so the agent property lines up with skill and command events; unknown agent types fall back to the stored string rather than dropping the signal. - CheckpointCondensedSignal doc now states the actual invariant (content-free metadata, no file paths/prompts/transcript content) instead of "booleans and counts only", and documents Agent as the registry key. - BuildCheckpointCondensedPayload defaults an empty agent to "auto" like the other payload builders; the shared literal moved to the autoAgentName const. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
cdd3faf to
0fe22f1
Compare
Address Copilot review on #2024: - emitCheckpointCondensedTelemetry resolves state.AgentType (display name, "Claude Code") to the agent registry key ("claude-code") so the agent property lines up with skill and command events; unknown agent types fall back to the stored string rather than dropping the signal. - CheckpointCondensedSignal doc now states the actual invariant (content-free metadata, no file paths/prompts/transcript content) instead of "booleans and counts only", and documents Agent as the registry key. - BuildCheckpointCondensedPayload defaults an empty agent to "auto" like the other payload builders; the shared literal moved to the autoAgentName const. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0fe22f1 to
fc10900
Compare
Raw search-command counts can't say whether adoption is low — most sessions have nothing worth searching for. The denominator that makes the rate meaningful is: sessions that edited files carrying AI checkpoint history without ever consulting search. Entire is the only tool that can compute it, and until now nothing emitted it. On each successful post-commit condensation, emit cli_checkpoint_condensed with two booleans and a count: used_search (substring probe over the already in-memory transcript, canonical and legacy spellings), prior_ai_history (did any committed file appear in a recent commit carrying an Entire-Checkpoint trailer — one bounded git-log subprocess, --skip=1 to exclude the commit just made), and files_committed. Content-free by construction: no file paths, prompts, or transcript content leave the machine. Gated on the opt-in telemetry setting before any work happens — the git-log probe never runs for opted-out users — plus ENTIRE_TELEMETRY_OPTOUT; the PostHog call itself is the existing detached-child path and never blocks the hook. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Address Copilot review on #2024: - emitCheckpointCondensedTelemetry resolves state.AgentType (display name, "Claude Code") to the agent registry key ("claude-code") so the agent property lines up with skill and command events; unknown agent types fall back to the stored string rather than dropping the signal. - CheckpointCondensedSignal doc now states the actual invariant (content-free metadata, no file paths/prompts/transcript content) instead of "booleans and counts only", and documents Agent as the registry key. - BuildCheckpointCondensedPayload defaults an empty agent to "auto" like the other payload builders; the shared literal moved to the autoAgentName const. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…h -z
Three review findings:
The env opt-out was only checked inside TrackCheckpointCondensedDetached,
after the git-log density probe had already run — a user who opted in
previously and then set ENTIRE_TELEMETRY_OPTOUT still paid for the repository
scan. The emit path now checks the new telemetry.IsEnvOptedOut() before any
work (and the other trackers share the helper).
emitCheckpointCondensedTelemetry ran inside condenseAndUpdateState, i.e.
inside PostCommit's MutateSessionState closure, extending the session gate
hold with settings I/O, a git subprocess, machine-ID lookup, and a process
spawn. Condensation now snapshots a cheap condensedTelemetrySignal while
locked; the PostCommit loop emits it after the session's mutation saves,
alongside the skill-event emission.
git log --name-only quotes non-ASCII paths ("caf\303\251.go"), so
prior_ai_history could never match their unquoted FilesTouched form, and
line-based parsing could not represent names containing newlines. The probe
now passes -z and splits the NUL-terminated, unquoted name list.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fc10900 to
279f542
Compare
cli_checkpoint_condensed promises "a checkpoint was condensed" and delivers "a commit condensed a checkpoint". That gap already produced a HIGH review finding reading the absent emission on the doctor and session-end condensation paths as data loss, and it would mislead anyone querying total condensations. Every property is commit-scoped — files_committed counts that commit's files, prior_ai_history asks whether commits before this one touched them (hence the git-log probe's --skip=1) — so name the event for the commit: cli_commit_condensed, with the Go identifiers following. Renaming is free exactly now: this PR is unmerged, so the event has never been emitted in a release and no dashboard keys on it. After it ships it stops being free. Also records the scoping invariant on newCommitCondensedSignal: the sole caller is condenseAndUpdateState via postCommitProcessSessionLocked, and folding in the commit-less paths would add rows indistinguishable from genuine misses — inflating the denominator rather than completing it. Covering them is a metric change (trigger discriminator, nullable commit-scoped fields), not a bug fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@evisdren sorry — I pushed after your approval and dismissed it. Here's what changed so you can re-review just the delta. One new commit: Why. A review pass on #2100 filed two HIGH findings reading the absent emission on the doctor and session-end condensation paths as data loss. That reading is the name's fault: the event promises "a checkpoint was condensed" and delivers "a commit condensed a checkpoint". Every property is commit-scoped — Why now. Renaming is free only while this PR is unmerged: the event has never been emitted in a release and no dashboard keys on it. After merge it stops being free, which is why I didn't leave it as a follow-up.
Review shortcut: Lint clean, |
|
Measured over 328 local Claude Code transcripts older than 3 days: 18 would set The false positives are structural:
The body calls the looseness acceptable for an aggregate boolean, and I agree with that in principle — but the direction is the problem. Inflating Same-cost fix: match an actual tool invocation rather than a substring. The transcript is already in memory and already walked for Three smaller findings (per-session |
https://entire.io/gh/entireio/cli/trails/1071
Stacked on #2023.
Raw search-command counts can't say whether adoption is low — most sessions have nothing worth searching for. The denominator that makes the rate meaningful is: sessions that edited files carrying AI checkpoint history without ever consulting search. Entire is the only tool that can compute it, and until now nothing emitted it.
On each successful post-commit condensation, emit
cli_checkpoint_condensedwith two booleans and a count:used_search— substring probe over the already-in-memory transcript (canonicalentire searchand legacyentire checkpoint searchspellings). Deliberately loose (a prompt merely mentioning the command matches); acceptable for an aggregate boolean, never used for behavior.prior_ai_history— did any committed file appear in a recent commit carrying anEntire-Checkpointtrailer? One boundedgit log --skip=1 -n 50 --name-onlysubprocess;--skip=1excludes the commit just made.files_committed— count only.Content-free by construction: no file paths, prompts, or transcript content leave the machine. Gated on the opt-in telemetry setting before any work happens — the git-log probe never runs for opted-out users — plus
ENTIRE_TELEMETRY_OPTOUT; the PostHog call is the existing detached-child path and never blocks the hook.Tests: trailer-intersect semantics against a real temp repo (AI-touched vs human-touched vs HEAD-only files), transcript probe table test, payload shape + content-free assertions. Full suite: 9,170 unit / 492 integration / canary green.
🤖 Generated with Claude Code
Event name (added after review)
The event is
cli_commit_condensed, notcli_checkpoint_condensed. A reviewpass on the PR above this one read the absent emission on the doctor and
session-end condensation paths as data loss, which is the name's fault: it
promises "a checkpoint was condensed" and delivers "a commit condensed a
checkpoint".
Every property is commit-scoped —
files_committedcounts that commit's files,and
prior_ai_historyasks whether commits before this one touched them,which is what the probe's
--skip=1is for — so the event is named for thecommit, with the Go identifiers following. Renaming is free only while this PR
is unmerged: the event has never been emitted in a release and no dashboard
keys on it.
newCommitCondensedSignalnow carries the scoping invariant: its sole caller iscondenseAndUpdateState, reached only frompostCommitProcessSessionLocked.The commit-less condensation paths deliberately emit nothing, because rows
without a commit are indistinguishable from genuine misses and would inflate
the denominator rather than complete it. Covering them would need a
triggerdiscriminator plus nullable commit-scoped fields — a change to what the metric
means, not a bug fix.