Skip to content

feat(telemetry): missed-opportunity signal at checkpoint condensation - #2024

Open
jdx wants to merge 4 commits into
mainfrom
jdx/missed-opportunity-signal
Open

feat(telemetry): missed-opportunity signal at checkpoint condensation#2024
jdx wants to merge 4 commits into
mainfrom
jdx/missed-opportunity-signal

Conversation

@jdx

@jdx jdx commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

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_condensed with two booleans and a count:

  • used_search — substring probe over the already-in-memory transcript (canonical entire search and legacy entire checkpoint search spellings). 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 an Entire-Checkpoint trailer? One bounded git log --skip=1 -n 50 --name-only subprocess; --skip=1 excludes 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, not cli_checkpoint_condensed. A review
pass 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_committed counts that commit's files,
and prior_ai_history asks whether commits before this one touched them,
which is what the probe's --skip=1 is for — so the event is named for the
commit, 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.

newCommitCondensedSignal now carries the scoping invariant: its sole caller is
condenseAndUpdateState, reached only from postCommitProcessSessionLocked.
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 trigger
discriminator plus nullable commit-scoped fields — a change to what the metric
means, not a bug fix.

Copilot AI lite review requested due to automatic review settings August 17, 2026 20:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_condensed telemetry event payload and detached sender (CheckpointCondensedSignal, builder, tracker).
  • Adds strategy-side signal computation: used_search (substring probe over in-memory transcript) and prior_ai_history (bounded git log scan for recent commits with an Entire-Checkpoint trailer 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.

Comment thread cmd/entire/cli/telemetry/detached.go Outdated
Comment on lines +240 to +244
// 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
Comment on lines +94 to +103
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)
@jdx
jdx marked this pull request as ready for review August 17, 2026 20:37
@jdx
jdx requested a review from a team as a code owner August 17, 2026 20:37
jdx added a commit that referenced this pull request Aug 17, 2026
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>
@jdx
jdx force-pushed the jdx/missed-opportunity-signal branch from 3d4737d to 6f6eda2 Compare August 17, 2026 20:48
@jdx

jdx commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Both findings addressed in 6f6eda2:

  • Agent registry key: emitCheckpointCondensedTelemetry now resolves state.AgentType (display name, "Claude Code") to the registry key ("claude-code") via agent.GetByAgentType, 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.
  • Doc wording: CheckpointCondensedSignal now states the actual invariant — content-free metadata (no file paths, prompts, or transcript content) — and documents Agent as the registry key. Also applied the shared "auto" defaulting to this payload builder for consistency.

Rebased on the updated #2023.

@jdx
jdx requested a balanced review from Copilot August 17, 2026 21:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the git log probe. A user who opted in previously and then sets ENTIRE_TELEMETRY_OPTOUT still 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 MutateSessionState callback at lines 1004-1010, so settings I/O, a git log subprocess, 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 after MutateSessionState completes.
	// 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_history incorrect for ordinary non-ASCII paths (for example, Git outputs "caf\303\251.go" while FilesTouched contains café.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.

jdx added a commit that referenced this pull request Aug 18, 2026
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>
@jdx
jdx force-pushed the jdx/missed-opportunity-signal branch from 6f6eda2 to d96d143 Compare August 18, 2026 17:17
jdx added a commit that referenced this pull request Aug 19, 2026
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>
@jdx
jdx force-pushed the jdx/missed-opportunity-signal branch from d96d143 to 5a79a16 Compare August 19, 2026 18:04
@jdx

jdx commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the three remaining review findings in 5a79a16, and rebased onto the updated #2023:

  • Env opt-out checked before any probe work: emitCheckpointCondensedTelemetry now gates on the new telemetry.IsEnvOptedOut() before settings load and the git-log scan, so a user who opted in previously and then set ENTIRE_TELEMETRY_OPTOUT never pays for the repository probe. The other trackers share the helper.
  • Emission moved outside the session gate: condensation now only snapshots a cheap, I/O-free condensedTelemetrySignal while locked; the PostCommit loop emits it after the session's MutateSessionState saves, alongside the skill-event emission — same pattern the previous round established on feat(telemetry): emit skill invocations as cli_skill_invoked events #2023.
  • prior_ai_history no longer false-negatives on non-ASCII paths: the probe passes -z, so names arrive unquoted and NUL-terminated (café.go matches its FilesTouched form, and names containing newlines survive parsing). Pinned by a new test committing a non-ASCII path under a checkpoint trailer.

Full suite re-run on the stack: 9,324 unit / 514 integration / canary green.

jdx added a commit that referenced this pull request Aug 19, 2026
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>
@jdx
jdx force-pushed the jdx/missed-opportunity-signal branch from 5a79a16 to 7d418f4 Compare August 19, 2026 21:34
jdx added a commit that referenced this pull request Aug 20, 2026
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>
@jdx
jdx force-pushed the jdx/missed-opportunity-signal branch from 7d418f4 to cdd3faf Compare August 20, 2026 01:27
jdx added a commit that referenced this pull request Aug 21, 2026
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>
@jdx
jdx force-pushed the jdx/missed-opportunity-signal branch from cdd3faf to 0fe22f1 Compare August 21, 2026 13:17
jdx added a commit that referenced this pull request Aug 21, 2026
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>
@jdx
jdx force-pushed the jdx/missed-opportunity-signal branch from 0fe22f1 to fc10900 Compare August 21, 2026 13:29
Base automatically changed from jdx/skill-telemetry to main August 21, 2026 18:17
jdx and others added 3 commits August 21, 2026 13:17
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>
evisdren
evisdren previously approved these changes Aug 21, 2026
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>
@jdx

jdx commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

@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: 26c4cd871, a rename. cli_checkpoint_condensedcli_commit_condensed, with the Go identifiers following (CommitCondensedSignal, BuildCommitCondensedPayload, TrackCommitCondensedDetached, emitCommitCondensedTelemetry, newCommitCondensedSignal, commitCondensedSignal).

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 — files_committed counts that commit's files, and prior_ai_history asks whether commits before this one touched them, which is exactly what the probe's --skip=1 is for. So the event is now named for the commit that is part of its identity.

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.

newCommitCondensedSignal also now carries the scoping invariant — its sole caller is condenseAndUpdateState, reached only from postCommitProcessSessionLocked. The commit-less condensation paths deliberately emit nothing: rows without a commit are indistinguishable from genuine misses and would inflate the denominator rather than complete it. Covering them would need a trigger discriminator plus nullable commit-scoped fields — a change to what the metric means, not a bug fix. Both findings are dismissed with that rationale recorded.

Review shortcut: git diff 279f54263 26c4cd871 is the whole delta since your approval — 4 files, and apart from comments the only non-mechanical line is the event-name string itself.

Lint clean, test:ci green (unit + integration + canary).

@Soph

Soph commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

used_search is a raw substring probe, and on real transcripts it is dominated by false positives.

Measured over 328 local Claude Code transcripts older than 3 days: 18 would set used_search=true; 1 actually ran the command (Bash tool_use command match).

The false positives are structural:

  • setup_search_skill.go:96,105 puts entire search --json in the search skill's description and body — Entire installs the artifact that trips its own probe (3 of the 18).
  • investigate/prompt.go:84 injects entire search "<phrase>" --json into every investigate prompt.
  • Any session that reads this repo's own source (search_cmd.go, checkpoint_group.go, explain.go, cell_target.go) or merely discusses the command.

The body calls the looseness acceptable for an aggregate boolean, and I agree with that in principle — but the direction is the problem. Inflating used_search deflates the missed-opportunity rate, so the metric reads "adoption is fine" for the wrong reason, with nothing in the data to show it.

Same-cost fix: match an actual tool invocation rather than a substring. The transcript is already in memory and already walked for tool_use blocks (claudecode.ExtractSkillEvents); matching a Bash command field against entire (checkpoint )?search is what produced the "1" above.

Three smaller findings (per-session git log, the reintroduced telemetry-gate idiom, %B delimiter escaping) are on trail #1071.

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants