Documentation
¶
Overview ¶
Package telemetry — host fingerprint collector.
Microsoft-level diagnostics within strict legal/privacy limits: every dimension is either an enumerable bucket (CPU-count band, memory tier, locale-language head), a public process attribute (Go runtime version, GOOS, GOARCH), or a presence-bool (does CLI X exist on PATH). NOTHING per-user-identifiable. NO paths, NO env values, NO hostnames. Operator can `clawtool telemetry preview` to see the exact wire shape before opting in.
Wire shape: one event per daemon boot, `clawtool.host_fingerprint`, carrying every dimension this file collects. Keeps PostHog events- per-session bounded (server.start + host_fingerprint + per-call dispatch + log events) instead of per-property explosion.
Package telemetry — daemon log forwarder. The daemon's combined stdout/stderr lands in $XDG_STATE_HOME/clawtool/daemon.log. Every goroutine panic, every "clawtool: <subsystem>: <error>" stderr line, every BIAM reap warning ends up there — but it's local- only, so a daemon stuck in a panic loop on someone else's host is invisible to us until they file an issue.
LogWatcher tails the daemon log starting from EOF (so we never stream the historical buffer), classifies lines into severity + event_kind taxonomies, redacts known secret shapes, rate- limits to keep a panicking daemon from flooding PostHog, and emits `clawtool.daemon.log_event` events through the existing telemetry client. NO log-line bodies cross the wire — only the classification fields, so an env-value or path that happens to be in the log can't leak.
Wired in server.go after telemetry.New: one watcher per daemon boot, cancelled via context on shutdown.
Package telemetry — anonymous, opt-in PostHog event emission for clawtool (ADR-014 F5, gemini's R4 pick).
Strict guarantee: never emits prompts, paths, file contents, secrets, or env values. The CLI dispatcher strips arg slices before passing to Track; we additionally allow-list the keys that can ride on a payload.
Per ADR-007 we wrap github.com/posthog/posthog-go. The client is nil-safe; passing nil to Track is a no-op so call sites don't need to gate every call.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CIDisabled ¶ added in v0.22.81
func CIDisabled() bool
CIDisabled reports whether the current process is running on a CI runner AND the maintainer opt-in `CLAWTOOL_TELEMETRY_FORCE_CI` env var is NOT set. CI runners pollute production analytics (~95% of the events the operator pulled from PostHog on 2026-04-30 came from CI hosts running pseudo-version Go-cached binaries) so the default-off gate keeps that noise off the production project.
Opt-in semantics: setting `CLAWTOOL_TELEMETRY_FORCE_CI=1` (or `true` / `on`) re-enables emission for the maintainer's own release-tracking workflows that legitimately want to send events from CI. Default off — every other downstream CI runner stays silent without configuration. Read live (not cached at package init) so tests + a single daemon's lifetime can manipulate the env via t.Setenv without re-instantiating the package.
func EmitInstallOnce ¶ added in v0.22.4
EmitInstallOnce fires a `clawtool.install` event the first time it's called on a host AND the telemetry client is enabled. A marker file under $XDG_DATA_HOME/clawtool/install-emitted ensures every subsequent call is a no-op. Daemon boot is the natural place to call this — by the time `clawtool serve` runs on a fresh install we've already initialised the telemetry client and the marker can be created safely.
install_method comes from $CLAWTOOL_INSTALL_METHOD which the install.sh / brew formula / go install wrapper sets at install time. Empty / unrecognised falls through to "unknown" so we still get the event, just without source attribution.
The marker write happens BEFORE the Track call so a posthog outage can't cause repeated events on each retry. Worst case: we lose one install event entirely. Better than counting a single install ten times because the network was flaky.
func FingerprintProps ¶ added in v0.22.37
FingerprintProps returns the property map for a single clawtool.host_fingerprint event. Every value is either:
- an integer count (cpu_count) or coarse bucket string
- a fixed-cardinality enum (locale_lang, term_kind, install_method)
- a presence boolean (claude_code_present, etc.)
- a public runtime attribute (go_version)
Caller passes the boot-time install method (already known to server.go via $CLAWTOOL_INSTALL_METHOD) so we don't re-resolve it.
func SetDebug ¶ added in v0.22.34
func SetDebug(on bool)
SetDebug toggles the debug trace at runtime. Wired from `clawtool serve --debug` so the operator can flip it without touching env.
func SilentDisabled ¶
func SilentDisabled() bool
SilentDisabled tells callers whether the env var explicitly disables telemetry regardless of config (for the "kill switch" use case operators want before talking on conference Wi-Fi).
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client wraps a PostHog client + the per-host anonymous distinct ID. Nil-safe: `(*Client)(nil).Track(...)` is a clean no-op.
sessionID groups every event emitted from a single daemon / CLI invocation under one $session_id property — PostHog's Sessions view + funnel queries rely on this to reconstruct "user did A then B then C in the same run" rather than treating every event as an isolated row. Generated fresh on New(), so a daemon restart starts a new session (which is the right boundary for CLI tools — different invocations are different units of work).
func New ¶
func New(cfg config.TelemetryConfig) *Client
New initialises the client when telemetry is enabled. Disabled config returns a nil-friendly client (Track is a no-op). Init failures degrade silently — telemetry is never load-bearing.
API key precedence: cfg.APIKey > cogitavePostHogKey baked-in default. Same for host. Operator-provided values always win so a self-hosted PostHog instance can capture the data instead of the shared cogitave project.
func (*Client) Enabled ¶
Enabled reports whether the client will actually emit. Useful for hot-path skips on expensive payload construction.
func (*Client) Track ¶
Track emits one event. Properties outside the allow-list are silently dropped. Safe to call on a nil receiver.
The c.client nil-check happens under c.mu so a Track racing a Close (which sets c.client = nil) can't dereference a nil posthog.Client. Pre-fix this checked nil OUTSIDE the lock then called Enqueue inside the lock — a Close that won the lock-race nil'd the field, and the next Track passed the outside-check only to nil-deref under the lock.
type LogWatcher ¶ added in v0.22.36
type LogWatcher struct {
// contains filtered or unexported fields
}
LogWatcher tails a log file and forwards classified events to a telemetry client. One watcher per daemon process. Run is the blocking entrypoint; cancel via the context.
func NewLogWatcher ¶ added in v0.22.36
func NewLogWatcher(tc *Client, path string) *LogWatcher
NewLogWatcher constructs a watcher. tc may be nil (no-op) or a disabled client (also no-op — the Track method short-circuits). path is the daemon log path (typically daemon.LogPath()).
func (*LogWatcher) Run ¶ added in v0.22.36
func (w *LogWatcher) Run(ctx context.Context)
Run blocks until ctx is cancelled. Tails path from EOF, classifies each new line, redacts content, emits classification-only events at most logEventPerMinuteCap per minute. Open errors are logged once via the debug seam and the watcher exits — there's no daemon log on a fresh host until the daemon writes its first line, but server.go arranges for that to happen before this is called.