telemetry

package
v3.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 4, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package telemetry defines event shapes and helpers for CLI usage telemetry.

Two event types are emitted per command invocation:

  • CommandIntent: fired before execution captures command shape and runtime environment — never user-supplied values.
  • CommandError: fired only on failure, after CommandIntent, captures an error category enum and elapsed time.

Neither event records flag values, positional args, env var values, or any other user-supplied data. [FlagsSet] contains flag names only, and cobra's Visit function means only explicitly-set declared flags appear.

Usage:

telemetry.SetBackend(myBackend)       // once at startup
env := telemetry.NewEnv()             // once at startup
telemetry.RecordIntent(CommandIntent{...})
if err != nil {
    telemetry.RecordError(CommandError{...})
}

Index

Constants

View Source
const (
	AgentClaudeCode = "claude-code"
	AgentCursor     = "cursor"
	AgentGeminiCLI  = "gemini-cli"
	AgentCodexCLI   = "codex-cli"
	AgentAugment    = "augment"
	AgentCline      = "cline"
	AgentOpenCode   = "opencode"
	AgentTrae       = "trae"
	AgentDevin      = "devin"
)

AI agent identifiers. These are the values sent in the "aiagent" telemetry property — lowercase, hyphenated, stable across agent versions.

View Source
const (
	InstallTypeScript  = "script"
	InstallTypeBrew    = "brew"
	InstallTypeUnknown = "unknown"
)

InstallType values. Add new install methods here as they're wired up, and have the installer set hbase.EnvInstallType accordingly.

Variables

This section is empty.

Functions

func DetectAgent

func DetectAgent() string

DetectAgent returns a standardized, lowercase identifier for the coding agent the CLI appears to be running under, or "" if none is detected. Detection relies on env vars each agent sets in its own subprocesses.

func Disabled

func Disabled() bool

Disabled reports whether the user has opted out of telemetry, via either disable_telemetry in config.yaml or HARNESS_NO_TELEMETRY=1. Distinct from having no active backend (e.g. a dev build with no write key) — that case still logs debug output, whereas an explicit opt-out logs nothing.

func Init

func Init() (flush func())

Init sets up the telemetry backend from config and returns a flush function to defer in main. Safe to call unconditionally — no-ops when telemetry is disabled or no write key is present.

func RecordError

func RecordError(e CommandError)

RecordError emits a CommandError. Same gating as RecordIntent.

func RecordInstall

func RecordInstall(e InstallEvent)

RecordInstall emits an InstallEvent. Same gating as RecordIntent.

func RecordIntent

func RecordIntent(e CommandIntent)

RecordIntent emits a CommandIntent. No-op when the user has opted out (disable_telemetry or HARNESS_NO_TELEMETRY=1) — in that case it returns before even logging the debug line. When opted in but no backend is set (dev build, no write key), it still logs for debugging but sends nothing.

func ResolveInstallType

func ResolveInstallType() string

ResolveInstallType reads hbase.EnvInstallType, defaulting to InstallTypeScript when unset and falling back to InstallTypeUnknown for any value outside the whitelist.

func SetBackend

func SetBackend(b Backend)

SetBackend registers the active sink. Pass nil to disable. Call before any command executes.

func SetDisabled

func SetDisabled(v bool)

SetDisabled sets the disabled flag from config.yaml's disable_telemetry field. Call once at startup after loading config.

func UserDomainFromEmail

func UserDomainFromEmail(email string) string

UserDomainFromEmail extracts the domain portion of an email address (e.g. "harness.io"). Returns empty string if email is empty or malformed.

Types

type Backend

type Backend interface {
	RecordIntent(e CommandIntent)
	RecordError(e CommandError)
	RecordInstall(e InstallEvent)
}

Backend is implemented by telemetry sinks (Segment, debug-stdout, etc.).

type CommandError

type CommandError struct {
	// Mirror of CommandIntent identity fields for correlation.
	Verb       string
	Noun       string
	Module     string
	AccountID  string
	UserDomain string
	UserID     string
	UserType   string
	TokenKind  string
	AuthSource string
	RunID      string

	Category   ErrorCategory
	DurationMs int64

	Env Env
}

CommandError is emitted when a command exits with an error, paired with a prior CommandIntent for the same invocation.

type CommandIntent

type CommandIntent struct {
	// Verb/Noun/Module describe the command shape, e.g. "execute"/"pipeline"/"pipeline".
	Verb   string
	Noun   string
	Module string

	// FlagsSet holds the names of flags the user explicitly passed.
	// Collected via cobra's cmd.Flags().Visit — only declared flags, never values.
	FlagsSet []string

	// AccountID from resolved auth. Empty for commands that skip auth.
	AccountID string

	// UserDomain is the domain portion of the user's profile email (e.g. "harness.io").
	// Never the full email address.
	UserDomain string

	// UserID is the Harness user uuid from the resolved profile, or "" if unset.
	UserID string

	// UserType is config.UserTypeUser or config.UserTypeServiceAccount, or "" if unset.
	UserType string

	// TokenKind is the type of credential in use: "pat", "sat", "jwt", or "".
	TokenKind string

	// AuthSource is "profile" when auth came from config file, "env" when from env vars.
	AuthSource string

	// RunID correlates all API calls from this invocation. Mirrors hbase.RunID.
	RunID string

	Env Env
}

CommandIntent is emitted once per invocation before the command executes. It records who is running what and which flags were explicitly set.

type Env

type Env struct {
	OS      string // runtime.GOOS
	Arch    string // runtime.GOARCH
	Version string // hbase.Version

	IsDev               bool
	IsTTY               bool // stdout is an interactive terminal
	IsPipelineExecution bool
	PipelineID          string // HARNESS_PIPELINEID; empty when IsPipelineExecution is false

	// AIAgent is a standardized identifier for the coding agent the CLI is
	// running under (e.g. "claude-code", "cursor"), or "" if none is detected.
	// See [DetectAgent].
	AIAgent string

	Locale string // from LANG/LC_ALL/LC_CTYPE, e.g. "en_US.UTF-8"

	TermProgram string // TERM_PROGRAM env var, e.g. "vscode", "iTerm.app"
}

Env captures static facts about the runtime environment. Call NewEnv once at startup and reuse the result across all events.

func NewEnv

func NewEnv() Env

NewEnv captures the current runtime environment. Call once at startup.

type ErrorCategory

type ErrorCategory string

ErrorCategory is a coarse, enum-safe classification of a command failure. It must never contain user-supplied text.

const (
	ErrorCategoryAuth        ErrorCategory = "auth_error"
	ErrorCategoryAPI         ErrorCategory = "api_error"
	ErrorCategoryNotFound    ErrorCategory = "not_found"
	ErrorCategoryValidation  ErrorCategory = "validation_error"
	ErrorCategoryInvalidVerb ErrorCategory = "invalid_verb"
	ErrorCategoryInvalidNoun ErrorCategory = "invalid_noun"
	ErrorCategoryInvalidFlag ErrorCategory = "invalid_flag"
	ErrorCategoryBadUsage    ErrorCategory = "bad_usage" // fallback when more specific bucket can't be determined
	ErrorCategoryTimeout     ErrorCategory = "timeout"
	ErrorCategoryUnknown     ErrorCategory = "unknown"
)

func ClassifyError

func ClassifyError(err error) ErrorCategory

ClassifyError maps err to an ErrorCategory without inspecting any user-supplied message text. It relies on typed sentinel errors.

Currently handles:

To classify API errors (401/403/404), the client package needs to expose a typed error carrying the HTTP status code. Until then those fall through to ErrorCategoryUnknown.

type InstallEvent

type InstallEvent struct {
	RunID string

	// InstallType identifies how the CLI was installed. See
	// [ResolveInstallType] for the whitelist and default.
	InstallType string

	Env Env
}

InstallEvent is emitted once by an installer, via the hidden --post-install flag, right after a fresh binary is placed on disk.

type SegmentBackend

type SegmentBackend struct {
	// contains filtered or unexported fields
}

SegmentBackend implements Backend by posting directly to Segment's HTTP tracking API, sending each event on its own goroutine so callers never block on network I/O. Create with [NewSegmentBackend] and call SegmentBackend.Close (or defer it) before the process exits to give any in-flight requests a brief chance to land — see [closeGracePeriod].

func (*SegmentBackend) Close

func (s *SegmentBackend) Close()

Close waits up to [closeGracePeriod] for any in-flight events to finish sending, then returns unconditionally. Call via defer in main before process exit.

func (*SegmentBackend) RecordError

func (s *SegmentBackend) RecordError(e CommandError)

RecordError sends a "Command Error" track event.

func (*SegmentBackend) RecordInstall

func (s *SegmentBackend) RecordInstall(e InstallEvent)

RecordInstall sends a "CLI Installed" track event.

func (*SegmentBackend) RecordIntent

func (s *SegmentBackend) RecordIntent(e CommandIntent)

RecordIntent sends a "Command Run" track event.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL