add checkpoint_policy_blocked telemetry event · Entire
add checkpoint_policy_blocked telemetry event
b8fddf4→main·
pfleidi·2w ago·2 files·+158 added/-0 removed
New event records when a hook is prevented from creating checkpoint data by a checkpoint policy. Pure payload builder mirrors BuildEventPayload; TrackCheckpointPolicyBlocked spawns the existing detached subprocess and honors ENTIRE_TELEMETRY_OPTOUT. The settings.Telemetry opt-in check stays on the CLI side.
Sessions
72027b27d255View transcript
[?
Add Checkpoint Policy Blocked TelemetryClaude Code·4 steps](/content/gh/entireio/cli/session/59674bd8-9edc-47fc-adae-9e93955ab9af#timeline-72027b27d255/index.html)
Changes
2
cmd/entire/cli/telemetry
Acheckpoint_policy.go+89
Acheckpoint_policy_test.go+69
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package telemetry
import (
"encoding/json"
"os"
"runtime"
"time"
"github.com/denisbrodbeck/machineid"
)
// Property values for the checkpoint_policy_blocked event.
const (
PolicyBlockedHookTypeGit = "git"
PolicyBlockedHookTypeAgent = "agent"
PolicyBlockedReasonUnsupported = "policy_unsupported"
PolicyBlockedReasonUnreadable = "policy_unreadable"
PolicyBlockedOutcomeSkipped = "skipped"
PolicyBlockedOutcomeBlocked = "blocked"
)
// CheckpointPolicyBlockedEvent carries the gate-derived fields for a
// checkpoint_policy_blocked telemetry event. Agent, CheckpointVersion, and
// CheckpointMinVersion are optional and omitted from the payload when empty.
type CheckpointPolicyBlockedEvent struct {
Hook string
HookType string
Reason string
Outcome string
Agent string
CheckpointVersion string
CheckpointMinVersion string
}
// BuildCheckpointPolicyBlockedPayload constructs the event payload. Exported for
// testing. Returns nil if the machine ID cannot be resolved.
func BuildCheckpointPolicyBlockedPayload(event CheckpointPolicyBlockedEvent, version string) *EventPayload {
machineID, err := machineid.ProtectedID("entire-cli")
if err != nil {
return nil
}
properties := map[string]any{
"hook": event.Hook,
"hook_type": event.HookType,
"reason": event.Reason,
"outcome": event.Outcome,
"cli_version": version,
"os": runtime.GOOS,
"arch": runtime.GOARCH,
}
if event.Agent != "" {
properties["agent"] = event.Agent
}
if event.CheckpointVersion != "" {
properties["checkpoint_version"] = event.CheckpointVersion
}
if event.CheckpointMinVersion != "" {
properties["checkpoint_min_version"] = event.CheckpointMinVersion
}
return &EventPayload{
Event: "checkpoint_policy_blocked",
DistinctID: machineID,
Properties: properties,
Timestamp: time.Now(),
}
}
// TrackCheckpointPolicyBlocked sends a checkpoint_policy_blocked event by
// spawning a detached subprocess. Best-effort and non-blocking; callers are
// responsible for the settings.Telemetry opt-in check. Honors
// ENTIRE_TELEMETRY_OPTOUT like the other trackers.
func TrackCheckpointPolicyBlocked(event CheckpointPolicyBlockedEvent, version string) {
if os.Getenv("ENTIRE_TELEMETRY_OPTOUT") != "" {
return
}
payload := BuildCheckpointPolicyBlockedPayload(event, version)
if payload == nil {
return
}
if payloadJSON, err := json.Marshal(payload); err == nil {
spawnDetachedAnalytics(string(payloadJSON))
}
}
Acmd/entire/cli/telemetry/checkpoint_policy.go+89
package telemetry
import "testing"
func TestBuildCheckpointPolicyBlockedPayload_Unsupported(t *testing.T) { t.Parallel() payload := BuildCheckpointPolicyBlockedPayload(CheckpointPolicyBlockedEvent{ Hook: "post-commit", HookType: PolicyBlockedHookTypeGit, Reason: PolicyBlockedReasonUnsupported, Outcome: PolicyBlockedOutcomeSkipped, CheckpointVersion: "v2", CheckpointMinVersion: "v2", }, "1.2.3") if payload == nil { t.Fatal("BuildCheckpointPolicyBlockedPayload returned nil") return } if payload.Event != "checkpoint_policy_blocked" { t.Errorf("Event = %q, want %q", payload.Event, "checkpoint_policy_blocked") } if payload.DistinctID == "" { t.Error("DistinctID must be set to the machine ID") } checks := map[string]any{ "hook": "post-commit", "hook_type": "git", "reason": "policy_unsupported", "outcome": "skipped", "checkpoint_version": "v2", "checkpoint_min_version": "v2", "cli_version": "1.2.3", } for k, want := range checks { if got := payload.Properties[k]; got != want { t.Errorf("Properties[%q] = %v, want %v", k, got, want) } } if _, ok := payload.Properties["agent"]; ok { t.Error("git-hook payload must not include 'agent'") } }
func TestBuildCheckpointPolicyBlockedPayload_UnreadableOmitsVersions(t *testing.T) { t.Parallel() payload := BuildCheckpointPolicyBlockedPayload(CheckpointPolicyBlockedEvent{ Hook: "session-start", HookType: PolicyBlockedHookTypeAgent, Reason: PolicyBlockedReasonUnreadable, Outcome: PolicyBlockedOutcomeSkipped, Agent: "claude-code", }, "1.2.3") if payload == nil { t.Fatal("BuildCheckpointPolicyBlockedPayload returned nil") return } if got := payload.Properties["agent"]; got != "claude-code" { t.Errorf("Properties[agent] = %v, want %q", got, "claude-code") } if _, ok := payload.Properties["checkpoint_version"]; ok { t.Error("unreadable payload must omit 'checkpoint_version'") } if _, ok := payload.Properties["checkpoint_min_version"]; ok { t.Error("unreadable payload must omit 'checkpoint_min_version'") } if got := payload.Properties["outcome"]; got != "skipped" { t.Errorf("Properties[outcome] = %v, want %q", got, "skipped") } }