Skip to content

Commit d4438b4

Browse files
committed
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.
1 parent b3f561f commit d4438b4

1 file changed

Lines changed: 61 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,67 @@ When adding a new command:
2424
1. Create the file under `cmd/<group>/`
2525
2. Register it in the group's `NewXxxCommand()` subcommands slice
2626
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.
38+
- `upgrade_event{from_version, to_version, success, failure_reason}``cmd/upgrade/`.
39+
40+
If you're adding a similar high-value lifecycle event (e.g. `deployment_event`, `vm_event`), follow the same pattern:
41+
```go
42+
if telemetry.Default != nil {
43+
telemetry.Default.Capture("<event_name>", map[string]any{
44+
"action": "<verb>",
45+
"success": true,
46+
// domain-specific props (NO secrets, NO file paths, NO emails)
47+
})
48+
}
49+
```
50+
51+
### Hard rules (do NOT break)
52+
53+
-**Never** import `github.com/posthog/posthog-go` outside `internal/telemetry/`.
54+
-**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> \
80+
-X github.com/NodeOps-app/createos-cli/internal/telemetry.PostHogHost=https://us.i.posthog.com" \
81+
-o /tmp/createos-test .
82+
/tmp/createos-test <your-command>
83+
# wait ~10s for posthog-go batch flush + 3s Shutdown
84+
# then query PostHog HogQL: SELECT event, properties FROM events WHERE timestamp > now() - INTERVAL 5 MINUTE
85+
```
86+
87+
Run the anti-pattern grep audit from the plan (`docs/superpowers/plans/2026-05-01-posthog-telemetry-plan.md` §Phase 7) before merging.
2788

2889
## API Client
2990

0 commit comments

Comments
 (0)