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
docs(claude): document telemetry conventions for new integrations
Add a Telemetry section to CLAUDE.md so future Claude sessions adding new
commands or integrations don't miss PostHog event wiring or accidentally
break the redaction / identity / Person-property invariants.
Covers:
- Most new commands need ZERO telemetry code (Action wrapper handles it).
- When to add a domain Capture call (lifecycle events only).
- Hard rules: no posthog-go imports outside internal/telemetry, no Flush
method, no PII in event payloads, no per-loop Capture calls.
- How to add a new sensitive flag (denyKeywords vs canonical name).
- How project_id auto-attaches and where to update resolveProjectID.
- How to smoke-test against a staging PostHog key.
Copy file name to clipboardExpand all lines: CLAUDE.md
+61Lines changed: 61 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -24,6 +24,67 @@ When adding a new command:
24
24
1. Create the file under `cmd/<group>/`
25
25
2. Register it in the group's `NewXxxCommand()` subcommands slice
26
26
3. Add it to the manual list in `root.go` Action (the home screen) in alphabetical order
27
+
4. Telemetry is automatic — see Telemetry section below.
28
+
29
+
## Telemetry
30
+
31
+
The CLI ships PostHog telemetry via `internal/telemetry`. **Most new commands need ZERO telemetry code** — `command_invoked` / `command_completed` / `command_failed` are emitted automatically by the Action wrapper in `cmd/root/telemetry.go` and the `main.go` finalizer. Just write your command's `Action` and it will be tracked.
32
+
33
+
### When you DO need to touch telemetry
34
+
35
+
Add a `telemetry.Default.Capture(...)` call ONLY when a command represents a discrete domain event distinct from "command invoked/completed". Examples already in the codebase:
36
+
37
+
-`auth_event{action: login|logout|refresh, method, success}` — login/logout/refresh in `cmd/auth/`, `cmd/root/root.go` Before hook.
- ❌ **Never** add a `Flush(timeout)` method to `internal/telemetry/Client`. posthog-go's `Close()` is terminal and there is no non-terminal flush primitive.
55
+
- ❌ **Never** include user email, file paths, command output, tokens, or any flag value matching the redact denylist (token/password/secret/key/credential/bearer/auth) in event Properties. The Action wrapper auto-redacts flag values via `internal/telemetry/redact.go`; preserve that behavior — if you add a new sensitive flag alias, ensure `internal/telemetry/redact.go::denyKeywords` covers it (canonical name OR alias).
56
+
- ❌ **Never** call `apiClient.GetUser()` outside of `cmd/auth/login.go`'s `bindIdentityAndCapture`. Identity binding happens once at login, not per-command.
57
+
- ❌ **Never** persist user_id/email/anything PII to `~/.createos/.identity` beyond `{user_id, aliased_for_user_id}`. The file is intentionally minimal; PostHog Person properties (email, name, signup_date) are sent in-memory via `Client.SetPersonProperties` and never touch disk.
58
+
- ❌ **Never** emit telemetry from `App.Before` (subcommand name not yet resolved) or `App.After` (cannot see Action error). Use the Action wrapper or the `main.go` finalizer.
59
+
- ❌ **Never** call `telemetry.Default.Capture` from a hot loop or per-iteration code path. Events are coarse-grained — one per CLI invocation, plus a handful of domain lifecycle events. The free monthly quota is 1M events.
60
+
61
+
### When adding a new sensitive flag
62
+
63
+
If you add a flag whose value should be redacted from telemetry (any new auth/secret-bearing flag):
64
+
- Pick a name where the canonical OR any alias contains a denylist keyword (`token`, `secret`, etc.) — e.g. `--api-token`, `--ssh-key`. The redact path canonicalizes via `c.Lineage()` so any alias matching the denylist redacts the whole flag.
65
+
- If the flag name doesn't naturally contain a denylist keyword (e.g. a credential called `--cookie`), add the new keyword to `internal/telemetry/redact.go::denyKeywords`.
66
+
67
+
### When adding a new project-scoped command
68
+
69
+
The Action wrapper auto-attaches `project_id` to events when:
70
+
- the command has a `--project` or `--project-id` flag, OR
71
+
- a `.createos.json` exists in cwd / parent dirs (`config.FindProjectConfig`).
72
+
73
+
If your command resolves project ID via a different mechanism (e.g. positional arg only, or a custom env var), update `cmd/root/telemetry.go::resolveProjectID` so the project_id property is set correctly.
74
+
75
+
### Verifying your changes
76
+
77
+
After wiring telemetry, smoke test against a staging key:
78
+
```bash
79
+
go build -ldflags="-X github.com/NodeOps-app/createos-cli/internal/telemetry.PostHogAPIKey=<STAGING_KEY> \
0 commit comments