You're reading the 0.10.0 archive.

Telemetry

Hankweave collects anonymous usage statistics to help improve the tool. This page is the reference for that system: what identity it uses, how to turn it off, what it does and does not send, which events exist, and where the data goes. If you only want to disable telemetry, jump to Opting out; if you want to know what leaves your machine before deciding, start with Privacy principles in practice.

Identity and reset#

Telemetry gives each machine an anonymous random UUID v4, called clientId, and persists it in a local identity file. The file contains clientId and createdAt, plus optional noticeShownAt and firstSuccessAt timestamps. firstSuccessAt records the first successful hank run and is also used by the TUI's one-time star nudge.

TYPESCRIPT
export interface TelemetryIdentity {
  clientId: string;
  createdAt: string;
  noticeShownAt?: string;
  firstSuccessAt?: string; // Tracks first successful hank run (for star nudge)
}

The default path is ~/.hankweave/telemetry.json. Set $HANKWEAVE_CACHE_DIR to use <cache-dir>/telemetry.json instead. The path resolution lives in two small functions:

TYPESCRIPT
function getTelemetryDir(): string {
  const cacheDir = process.env.HANKWEAVE_CACHE_DIR;
  if (cacheDir) {
    return cacheDir;
  }
  return path.join(os.homedir(), ".hankweave");
}

function getTelemetryFilePath(): string {
  return path.join(getTelemetryDir(), "telemetry.json");
}

getOrCreateClientId() creates the UUID and file on first use, then returns the stored ID. isFirstRun() is true when the file is absent; isFirstSuccess() is true when firstSuccessAt has not been recorded. Delete ~/.hankweave/telemetry.json, or the corresponding file under $HANKWEAVE_CACHE_DIR, to generate a new identity on the next run.

See Execution directory for related local paths and First run for the first-run flow.

Opting out before sending#

Telemetry is enabled by default, but several mechanisms can disable it, and they have a fixed precedence. The resolver applies the first matching rule below; later settings cannot re-enable telemetry after an earlier rule disables it.

TYPESCRIPT
export function resolveTelemetryConfig(fileConfig?: TelemetryConfig): ResolvedTelemetryConfig {
  // Priority 1: DO_NOT_TRACK=1 (universal standard)
  if (process.env.DO_NOT_TRACK === "1") {
    return {
      enabled: false,
      endpoint: DEFAULT_POSTHOG_HOST,
      debug: false,
      disabledReason: "DO_NOT_TRACK=1",
    };
  }

  // Priority 2: HANKWEAVE_TELEMETRY=0 or HANKWEAVE_TELEMETRY=false
  const telemetryEnv = process.env.HANKWEAVE_TELEMETRY;
  if (telemetryEnv === "0" || telemetryEnv === "false") {
    return {
      enabled: false,
      endpoint: DEFAULT_POSTHOG_HOST,
      debug: false,
      disabledReason: "HANKWEAVE_TELEMETRY=0",
    };
  }

  // Priority 3: CI detection (cannot be overridden in V1)
  if (isCI()) {
    return {
      enabled: false,
      endpoint: DEFAULT_POSTHOG_HOST,
      debug: false,
      disabledReason: "CI environment detected",
    };
  }

  // Priority 4: Config file
  if (fileConfig?.enabled === false) {
    return {
      enabled: false,
      endpoint: fileConfig.endpoint || DEFAULT_POSTHOG_HOST,
      debug: fileConfig.debug || false,
      disabledReason: "Disabled in config file",
    };
  }

  // Resolve endpoint from env or config
  const endpoint =
    process.env.HANKWEAVE_TELEMETRY_ENDPOINT || fileConfig?.endpoint || DEFAULT_POSTHOG_HOST;

  // Resolve debug from env or config
  const debug = process.env.HANKWEAVE_TELEMETRY_DEBUG === "1" || fileConfig?.debug || false;

  // Default: ENABLED
  return {
    enabled: true,
    endpoint,
    debug,
  };
Scroll to explore the table →
PriorityMechanismResultdisabledReason
1DO_NOT_TRACK=1DisabledDO_NOT_TRACK=1
2HANKWEAVE_TELEMETRY=0 or HANKWEAVE_TELEMETRY=falseDisabledHANKWEAVE_TELEMETRY=0
3CI environment detectedDisabledCI environment detected
4hankweave.json has telemetry.enabled: falseDisabledDisabled in config file
5No earlier rule matchesEnabled

The table after the code summarizes the same precedence: DO_NOT_TRACK wins over HANKWEAVE_TELEMETRY, which wins over CI detection, which wins over the config file. CI detection treats CI=true and CI=1 as CI, and also treats the presence of GITHUB_ACTIONS, TRAVIS, CIRCLECI, GITLAB_CI, JENKINS_URL, BUILDKITE, DRONE, CI_NAME, CODEBUILD_BUILD_ID, or TF_BUILD as CI. Because this check precedes the file setting, hankweave.json cannot re-enable telemetry through this configuration path while CI is detected.

The telemetry object in hankweave.json accepts optional enabled (boolean), endpoint (URI string), and debug (boolean) properties. Additional properties are rejected by the schema.

Scroll to explore the table →
fieldtypedefaultrequiredconstraintsdescription
telemetryobjectnoTelemetry configuration

The full object contract belongs to Hankweave JSON; the HANKWEAVE_TELEMETRY* variables belong to Environment variables. One wiring detail matters here: telemetry reads the raw telemetry key from hankweave.json through readFileTelemetryConfig(), because resolveSettings() strips that key from the effective runtime-configuration layer.

For the highest-priority opt-out, run:

⌁ Terminal
DO_NOT_TRACK=1 bunx hankweave@0.10.0

Privacy principles in practice#

The telemetry source documents three transformation principles – content becomes size, paths become counts, and IDs become hashes. An eight-category privacy-handling type applies those decisions field by field; in practice, error messages become failure_type values and environment variables become counts. The header comment and the category type below are the source of those rules:

TYPESCRIPT
/**
 * Telemetry Types and Schemas
 *
 * Privacy-preserving types for telemetry events.
 * All types follow the principle: content → size, paths → counts, IDs → hashes.
 */
TYPESCRIPT
type PrivacyHandling =
  | "include" // Include as-is (safe data like model names)
  | "hash" // SHA256 hash (for IDs we want to correlate)
  | "length" // Replace with character count
  | "count" // Replace with item count
  | "count_and_size" // Replace with count + byte size
  | "type_only" // Keep type/enum, drop details
  | "exclude" // Don't include at all
  | "nested"; // Has its own privacy-preserving version
Scroll to explore the table →
Documented principle or reductionRecorded form
Content → SizePrompt text becomes length_chars.
Paths → CountsFile paths become file_count and total_size_bytes.
IDs → HashesCodon, run, and loop IDs become SHA256 hashes.
In practice: messages → typesError messages become failure_type enum values.
In practice: secrets → countsEnvironment variables become { count: N }.

The eight field-handling categories are include, hash, length, count, count_and_size, type_only, exclude, and nested. TypeScript requires every field on Codon, Loop, and Run to appear in its privacy map; adding an unmapped field causes a compile error. The Codon map excludes name, hashes id, preserves selected configuration fields, counts file and environment-related fields, and turns prompt text into lengths or prompt-file fields into counts plus sizes. The privacy-preserving hank shape adds a SHA256 hank_hash and emits counts and hashes rather than prompt text, paths, or codon names.

For field ownership and execution semantics, see Codons and Events.

What is never collected#

The privacy maps exclude codon and loop name, runFolder, gitBranch, and serverPid. They reduce prompt text, system-prompt text, descriptions, file paths, environment variables, error messages, and rig setup to lengths, counts, hashes, types, or operation counts; raw content does not reach telemetry.

Error tracking has a separate scrubber. Before an error is sent, captureError() removes home-directory paths, API keys and tokens, KEY=VALUE patterns, and absolute paths from the error message and stack trace. This scrubber is distinct from the telemetry privacy maps.

The result is that a prompt appears as a measurement rather than its contents:

JSON
{
  "prompt": { "length_chars": "<count>" },
  "id_hash": "<SHA256 hash>"
}

See Privacy principles and Error tracking and scrubbing.

What is collected#

run_started carries a privacy-preserving hank structure: each item has a type, position, ID hash, model display name, continuation mode, prompt source and size, while the summary contains totals such as codons, loops, models, sentinels, and checkpointing.

run_completed carries a duration bucket (<1m, 1-5m, 5-15m, or 15m+), per-codon status and measurements, aggregate tool counts by name, and model-call counts by model ID. Per-codon measurements include duration, tokens, cost, sentinel measurements, and checkpoint flags.

Token usage is accumulated from token.usage events per codon and per model: input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens, and total_cost. tool.result events supply tool-call and tool-error counts per codon and at run level. Every event receives hankweave_version, os, os_version, arch, node_version, is_ci, and is_compiled as common user properties.

A collected codon therefore uses measurements and labels instead of source content:

JSON
{
  "type": "codon",
  "position": "<position>",
  "id_hash": "<SHA256 hash>",
  "model": "<model display name>",
  "prompt": { "source": "<inline|file|files>", "length_chars": "<count>" },
  "checkpointed_files": { "pattern_count": "<count>" }
}

See Events for the canonical event payload schemas and Budgets for budget semantics.

Event catalog and emitter status#

The shipped TelemetryEventName union declares 26 names in nine categories. Twenty-three have emitters; loop_iteration_started, continuation_started, and $ai_span are declared-only in hankweave 0.10.0. The union and the per-category status table below show both:

TYPESCRIPT
export type TelemetryEventName =
  // CLI events
  | "cli_init"
  | "cli_validate"
  | "cli_cleanup"
  | "cli_run"
  | "cli_help"
  // Run lifecycle
  | "run_started"
  | "run_completed"
  | "run_failed"
  | "run_crashed"
  // Codon lifecycle
  | "codon_started"
  | "codon_completed"
  | "codon_failed"
  | "codon_skipped"
  // Loop lifecycle
  | "loop_iteration_started"
  | "loop_iteration_completed"
  // Rig lifecycle
  | "rig_setup_completed"
  | "rig_setup_failed"
  // Recovery
  | "checkpoint_created"
  | "rollback_completed"
  | "continuation_started"
  // Sentinel
  | "sentinel_triggered"
  // Budget
  | "budget_set"
  | "budget_exceeded"
  // PostHog LLM Analytics (special $ prefixed events)
  | "$ai_generation"
  | "$ai_trace"
  | "$ai_span";
Scroll to explore the table →
CategoryNamesEmitter status
CLIcli_init, cli_validate, cli_cleanup, cli_run, cli_helpEmitted
Run lifecyclerun_started, run_completed, run_failed, run_crashedEmitted
Codon lifecyclecodon_started, codon_completed, codon_failed, codon_skippedEmitted
Loop lifecycleloop_iteration_started, loop_iteration_completedFirst declared-only; second emitted
Rig lifecyclerig_setup_completed, rig_setup_failedEmitted
Recoverycheckpoint_created, rollback_completed, continuation_startedFirst two emitted; last declared-only
Sentinelsentinel_triggeredEmitted
Budgetbudget_set, budget_exceededEmitted
PostHog LLM Analytics$ai_generation, $ai_trace, $ai_spanFirst two emitted; last declared-only

Emission timing differs by event kind. Early-exit CLI events (cli_init, cli_validate, cli_cleanup, cli_help) are sent immediately through sendCliTelemetry(), which creates a temporary collector and reads hankweave.json from the current directory for opt-out. The normal cli_run event is sent immediately by the main runtime collector's trackCliEvent() after configuration resolution and collector creation. Both paths are fire-and-forget and do not block the CLI. Runtime and codon events are queued from the server event stream and batch-sent at shutdown. $ai_generation is emitted once per completed codon, or once per model when a codon used multiple models; it carries $ai_trace_id, $ai_span_id, $ai_model, $ai_provider, $ai_input_tokens, $ai_output_tokens, $ai_cache_read_input_tokens, $ai_cache_creation_input_tokens, $ai_total_cost_usd, and $ai_latency (codon duration in seconds, or undefined when duration is absent). $ai_trace is emitted at shutdown for the full run with run-level latency and an error flag.

A compact LLM analytics payload has this shape:

JSON
{
  "event": "$ai_generation",
  "properties": {
    "$ai_trace_id": "<trace id>",
    "$ai_span_id": "<span id>",
    "$ai_model": "<model>",
    "$ai_provider": "<provider>",
    "$ai_input_tokens": "<count>",
    "$ai_output_tokens": "<count>",
    "$ai_cache_read_input_tokens": "<count>",
    "$ai_cache_creation_input_tokens": "<count>",
    "$ai_total_cost_usd": "<cost>",
    "$ai_latency": "<duration-seconds>"
  }
}

The canonical payload definitions are owned by Events. The generated catalog below describes the server events that feed runtime collection; it is not a second list of TelemetryEventName values.

Scroll to explore the table →
idcategoryjournaledsentinelRoutedpayloadFieldsreceipts
archive.completedserver-statetruetruecodonId, archivedPathsschemas/event-schemas.ts:668, schemas/event-schemas.ts:977, schemas/event-schemas.ts:1257, hankweave-runtime.ts:5465
archive.partialserver-statetruetruecodonId, archivedPaths, failedPathsschemas/event-schemas.ts:673, schemas/event-schemas.ts:978, schemas/event-schemas.ts:1258, hankweave-runtime.ts:5450
assistant.actionagentic-backbonetruetruecodonId, action, content, toolName?, toolInput?schemas/event-schemas.ts:572, schemas/event-schemas.ts:986, schemas/event-schemas.ts:1237, hankweave-runtime.ts:2777, hankweave-runtime.ts:2789, hankweave-runtime.ts:2824
budget.summaryserver-statetruetrueceiling, allocation, rows, totalsschemas/event-schemas.ts:724, schemas/event-schemas.ts:979, schemas/event-schemas.ts:1267, hankweave-runtime.ts:6478
checkpoint.listserver-statetruetruerunId, checkpoints, currentBranchschemas/event-schemas.ts:637, schemas/event-schemas.ts:968, schemas/event-schemas.ts:1250, hankweave-runtime.ts:4255
codon.completedserver-statetruetruecodonId, success, cost, duration, exitStatus, failureReason?, failureIgnored?, budgetExceeded?schemas/event-schemas.ts:560, schemas/event-schemas.ts:961, schemas/event-schemas.ts:1235, hankweave-runtime.ts:1886, hankweave-runtime.ts:2064, hankweave-runtime.ts:2253, hankweave-runtime.ts:3365
codon.extendedserver-statetruetruecodonId, codonName, extensionNumber, exhaustWithPrompt, cumulativeTokens, cumulativeCostschemas/event-schemas.ts:565, schemas/event-schemas.ts:962, schemas/event-schemas.ts:1236, hankweave-runtime.ts:2946
codon.startedserver-statetruetruecodonId, codonName, codonDescription?, sessionId, previousSessionId?, startTime, promptMetadata?schemas/event-schemas.ts:555, schemas/event-schemas.ts:960, schemas/event-schemas.ts:1234, hankweave-runtime.ts:316
errorserver-statetruetruemessage, codon?, fatal, severity?, context?, code?schemas/event-schemas.ts:617, schemas/event-schemas.ts:967, schemas/event-schemas.ts:1246, hankweave-runtime.ts:975, hankweave-runtime.ts:995, hankweave-runtime.ts:1019, hankweave-runtime.ts:1062, hankweave-runtime.ts:1090, hankweave-runtime.ts:1116, hankweave-runtime.ts:1814, hankweave-runtime.ts:1867, hankweave-runtime.ts:2045, hankweave-runtime.ts:2236, hankweave-runtime.ts:2609, hankweave-runtime.ts:3981, hankweave-runtime.ts:4177, hankweave-runtime.ts:4273, hankweave-runtime.ts:4349, hankweave-runtime.ts:4512, hankweave-runtime.ts:4536, hankweave-runtime.ts:4599, hankweave-runtime.ts:4769, hankweave-runtime.ts:4804, hankweave-runtime.ts:4896, hankweave-runtime.ts:4918, hankweave-runtime.ts:5016
file.updatedagentic-backbonetruetruepath, filename, content, actionschemas/event-schemas.ts:587, schemas/event-schemas.ts:988, schemas/event-schemas.ts:1240, hankweave-runtime.ts:2357, hankweave-runtime.ts:3927
filetree.updatedagentic-backbonetruetruetreeschemas/event-schemas.ts:592, schemas/event-schemas.ts:989, schemas/event-schemas.ts:1241, hankweave-runtime.ts:3954
history.batchconnection-statefalsefalseevents, hasMoreschemas/event-schemas.ts:688, schemas/event-schemas.ts:1012, schemas/event-schemas.ts:1260, hankweave-runtime.ts:1312
incomplete.codonconnection-statefalsefalsecodonId, codonName, messageschemas/event-schemas.ts:622, schemas/event-schemas.ts:1013, schemas/event-schemas.ts:1247
infoserver-statetruetruemessageschemas/event-schemas.ts:627, schemas/event-schemas.ts:966, schemas/event-schemas.ts:1248, hankweave-runtime.ts:1705, hankweave-runtime.ts:1728, hankweave-runtime.ts:1902, hankweave-runtime.ts:1933, hankweave-runtime.ts:1971, hankweave-runtime.ts:2080, hankweave-runtime.ts:2196, hankweave-runtime.ts:2269, hankweave-runtime.ts:2730, hankweave-runtime.ts:3082, hankweave-runtime.ts:3100, hankweave-runtime.ts:3181, hankweave-runtime.ts:3205, hankweave-runtime.ts:3474, hankweave-runtime.ts:3723, hankweave-runtime.ts:3755, hankweave-runtime.ts:4058, hankweave-runtime.ts:4328, hankweave-runtime.ts:6427
loop.iteration.completedserver-statetruetrueloopId, iteration, durationMs, costUsd, tokensUsed, isFinal, terminationReason?schemas/event-schemas.ts:612, schemas/event-schemas.ts:976, schemas/event-schemas.ts:1245, hankweave-runtime.ts:6035
pongconnection-statefalsefalsemessage, timestamp, clientId?schemas/event-schemas.ts:683, schemas/event-schemas.ts:1011, schemas/event-schemas.ts:1259, hankweave-runtime.ts:1215, hankweave-runtime.ts:1243
rig.outputagentic-backbonetruetruecodonId, stream, line, commandIndexschemas/event-schemas.ts:607, schemas/event-schemas.ts:992, schemas/event-schemas.ts:1244, hankweave-runtime.ts:6109
rig.setup.completedagentic-backbonetruetruecodonId, rigType, commandCount, durationMs, createdCheckpointschemas/event-schemas.ts:597, schemas/event-schemas.ts:990, schemas/event-schemas.ts:1242, hankweave-runtime.ts:2160
rig.setup.failedagentic-backbonetruetruecodonId, failureType, exitCode?, commandIndex?, ignoredschemas/event-schemas.ts:602, schemas/event-schemas.ts:991, schemas/event-schemas.ts:1243, hankweave-runtime.ts:1791
rollback.archiveRestoreserver-statetruetruecodonId, restoredPaths, failedPaths?, statusschemas/event-schemas.ts:678, schemas/event-schemas.ts:974, schemas/event-schemas.ts:1256, hankweave-runtime.ts:5557
rollback.codonCheckpointserver-statetruetruecodonId, codonName, checkpoint, checkpointType, messageschemas/event-schemas.ts:647, schemas/event-schemas.ts:971, schemas/event-schemas.ts:1252, hankweave-runtime.ts:4684, hankweave-runtime.ts:5136, hankweave-runtime.ts:5191
rollback.completedserver-statetruetruefromRun, toRun, checkpoint, codonId, codonName, checkpointType, autoRestartschemas/event-schemas.ts:662, schemas/event-schemas.ts:972, schemas/event-schemas.ts:1255, hankweave-runtime.ts:4722, hankweave-runtime.ts:5254
rollback.progressserver-statetruetruecurrentStep, totalSteps, messageschemas/event-schemas.ts:657, schemas/event-schemas.ts:970, schemas/event-schemas.ts:1254, hankweave-runtime.ts:5117, hankweave-runtime.ts:5156
rollback.rigCleanupserver-statetruetruecodonId, codonName, directories, status, successfulCleanups?, failedCleanups?, error?schemas/event-schemas.ts:652, schemas/event-schemas.ts:973, schemas/event-schemas.ts:1253, hankweave-runtime.ts:5624, hankweave-runtime.ts:5662, hankweave-runtime.ts:5678
rollback.startedserver-statetruetruefromRun, fromCodon, toCodon, toCheckpoint, checkpointType, codonsToProcessschemas/event-schemas.ts:642, schemas/event-schemas.ts:969, schemas/event-schemas.ts:1251, hankweave-runtime.ts:4645, hankweave-runtime.ts:5095
sentinel.errorsentineltruefalsesentinelId, codonId, errorType, message, retriable, consecutiveFailureCountschemas/event-schemas.ts:709, schemas/event-schemas.ts:1001, schemas/event-schemas.ts:1264, sentinels/sentinel.ts:576, sentinels/sentinel.ts:663, sentinels/sentinel.ts:798, sentinels/sentinel.ts:905
sentinel.loadedsentineltruefalsesentinelId, codonId, model, triggerType, executionStrategy, conversational, source, sourcePath?schemas/event-schemas.ts:699, schemas/event-schemas.ts:999, schemas/event-schemas.ts:1262, hankweave-runtime.ts:5819
sentinel.outputsentineltruefalsesentinelId, codonId, triggerNumber, outputType, content, cost, tokens, eventCountschemas/event-schemas.ts:714, schemas/event-schemas.ts:1002, schemas/event-schemas.ts:1265, sentinels/sentinel.ts:538, sentinels/sentinel.ts:635, sentinels/sentinel.ts:762, sentinels/sentinel.ts:877
sentinel.triggeredsentineltruefalsesentinelId, codonId, triggerNumber, strategy, eventCount, queueSizeschemas/event-schemas.ts:719, schemas/event-schemas.ts:1003, schemas/event-schemas.ts:1266, sentinels/sentinel.ts:392
sentinel.unloadedsentineltruefalsesentinelId, codonId, reason, errorType?, finalCost, llmCallCountschemas/event-schemas.ts:704, schemas/event-schemas.ts:1000, schemas/event-schemas.ts:1263, sentinels/sentinel-manager.ts:851
server.idleserver-statetruetruereason, messageschemas/event-schemas.ts:632, schemas/event-schemas.ts:964, schemas/event-schemas.ts:1249, hankweave-runtime.ts:941, hankweave-runtime.ts:3621, hankweave-runtime.ts:3791, hankweave-runtime.ts:3812, hankweave-runtime.ts:4072, hankweave-runtime.ts:4743, hankweave-runtime.ts:5279, hankweave-runtime.ts:5291
server.readyconnection-statefalsefalseserverVersion, executionPath, agentRootPath, dataPath, port, proxyPort?, outputDirectory?schemas/event-schemas.ts:545, schemas/event-schemas.ts:1010, schemas/event-schemas.ts:1232, hankweave-runtime.ts:916
state.snapshotserver-statetruetruecurrentCodon?, completedCodons, fileTree, totalCost, totalTime, recentFileAccess?, isRollingBackschemas/event-schemas.ts:550, schemas/event-schemas.ts:963, schemas/event-schemas.ts:1233, hankweave-runtime.ts:1422
state.transitionserver-statetruetruetransitionType, runId?, codonId?, transition, resultingStateschemas/event-schemas.ts:693, schemas/event-schemas.ts:975, schemas/event-schemas.ts:1261, hankweave-runtime.ts:394
token.usageserver-statetruetruecodonId, inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens, totalCost, modelId?, modelUsage?schemas/event-schemas.ts:577, schemas/event-schemas.ts:965, schemas/event-schemas.ts:1238, hankweave-runtime.ts:2646, hankweave-runtime.ts:2660
tool.resultagentic-backbonetruetruecodonId, toolUseId, toolName, result, truncated, originalLength, executionTimeMs, isErrorschemas/event-schemas.ts:582, schemas/event-schemas.ts:987, schemas/event-schemas.ts:1239, hankweave-runtime.ts:2906

Network and backend#

The default endpoint is https://hw-telemetry.southbridge.ai, a self-hosted PostHog instance. Override it with HANKWEAVE_TELEMETRY_ENDPOINT or hankweave.json telemetry.endpoint. The embedded PostHog API key, phc_hDo9EY9g5eB18EYqnR2etTcEKXId7Rw971hoKQDxo5A, is public and write-only; replace it with POSTHOG_API_KEY when configuring a different backend.

Telemetry sends are fire-and-forget: a slow or failing backend does not throw or block the user. The PostHog client uses flushAt: 1 and flushInterval: 0. Immediate CLI capture() calls flush rather than batch; runtime events are queued and sent at shutdown with captureMany(). Each flush has a two-second timeout, and failures are silent.

Output
HANKWEAVE_TELEMETRY_ENDPOINT=<endpoint-URI> bunx hankweave@0.10.0

See Troubleshooting for operational symptoms and Environment variables for the variable contract.

Debug mode without sending#

Set HANKWEAVE_TELEMETRY_DEBUG=1 or set hankweave.json telemetry.debug to true. Debug mode prints each event as [TELEMETRY DEBUG] <event_name>: { ... }, appends one JSON object per line to telemetry-debug.jsonl, and does not send the event to PostHog. The file is ~/.hankweave/telemetry-debug.jsonl by default and follows $HANKWEAVE_CACHE_DIR when set; the cache directory is created if needed.

⌁ Terminal
HANKWEAVE_TELEMETRY_DEBUG=1 bunx hankweave@0.10.0

The observable line shape is:

Output
[TELEMETRY DEBUG] <event_name>: { ... }

See Observe and debug and Environment variables.

First-run notice appears once#

After configuration resolution and before execution starts, the first run prints a boxed notice saying that Hankweave collects anonymous usage statistics. It says that personal information, file contents, and prompts are not collected, links to this page, names HANKWEAVE_TELEMETRY=0 as an opt-out, and says the notice will not be shown again. The notice is shown even when telemetry is disabled.

hasNoticeBeenShown() checks noticeShownAt, and markNoticeShown() writes it to the identity file. The source-defined text is:

TYPESCRIPT

const NOTICE_TEXT = `
┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│  Hankweave collects anonymous usage statistics to help          │
│  improve the tool. No personal information, file contents,      │
│  or prompts are collected.                                      │
│                                                                 │
│  Learn more: https://docs.hankweave.dev/reference/telemetry     │
│  Opt out:    export HANKWEAVE_TELEMETRY=0                       │
│                                                                 │
│  This notice won't be shown again.                              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
`;

// =============================================================================

See First run and Identity and reset.

Error tracking and scrubbing#

Uncaught exceptions and unhandled rejections are auto-captured through PostHog's enableExceptionAutocapture: true. Manual captureError() calls add failure and correlation context such as codonStatus, runStatus, errorCode, exitCode, failureType, runIdHash, and codonIdHash.

Before sending, the scrubber removes home-directory paths, API keys and tokens, KEY=VALUE patterns, and absolute paths from error messages and stack traces. It mutates the error's .message and .stack in place. Error tracking uses the same PostHog client as telemetry and flushes at shutdown with its own two-second timeout.

A manual capture can carry safe correlation fields:

TYPESCRIPT
captureError(error, {
  codonStatus: "<status>",
  runStatus: "<status>",
  errorCode: "<code>",
  runIdHash: "<SHA256 hash>",
  codonIdHash: "<SHA256 hash>"
});

See Troubleshooting and Errors and exit codes.

Integration at shutdown#

The CLI creates TelemetryCollector after configuration resolution, passes it to HankweaveRuntime with setTelemetryCollector(), and subscribes the collector to the runtime's server events.

At shutdown, sendRunTelemetry() reads the final run state, composes run_started, queued events, the applicable run_completed or run_failed event, and $ai_trace, then sends the batch with captureMany() before shutting down the client. Telemetry configuration is read directly from hankweave.json; resolveSettings() removes telemetry from the effective runtime-configuration layer.

The order is:

Output
// Pseudocode, not the implementation.
resolve config
create TelemetryCollector
runtime.setTelemetryCollector(collector)
runtime subscribes collector to server events
at shutdown:
  read final run state
  compose run_started + queued events + terminal event
  captureMany()
  client.shutdown()

See Runtime architecture, Events, and Hankweave JSON.