# 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](#opting-out); if you want to know what leaves your machine before deciding, start with [Privacy principles in practice](#privacy-principles).
## 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 `/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](/0.10.0/files/reference/execution-directory) for related local paths and [First run](/0.10.0/files/start/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,
};
```
| Priority | Mechanism | Result | `disabledReason` |
| -------: | ------------------------------------------------------ | -------- | ------------------------- |
| 1 | `DO_NOT_TRACK=1` | Disabled | `DO_NOT_TRACK=1` |
| 2 | `HANKWEAVE_TELEMETRY=0` or `HANKWEAVE_TELEMETRY=false` | Disabled | `HANKWEAVE_TELEMETRY=0` |
| 3 | CI environment detected | Disabled | `CI environment detected` |
| 4 | `hankweave.json` has `telemetry.enabled: false` | Disabled | `Disabled in config file` |
| 5 | No earlier rule matches | Enabled | – |
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.
| field | type | default | required | constraints | description |
| ----------- | -------- | ------- | -------- | ----------- | ----------------------- |
| `telemetry` | `object` | | no | | Telemetry configuration |
The full object contract belongs to [Hankweave JSON](/0.10.0/files/reference/hankweave-json); the `HANKWEAVE_TELEMETRY*` variables belong to [Environment variables](/0.10.0/files/reference/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:
```sh
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
```
| Documented principle or reduction | Recorded form |
| --------------------------------- | ------------------------------------------------------ |
| Content → Size | Prompt text becomes `length_chars`. |
| Paths → Counts | File paths become `file_count` and `total_size_bytes`. |
| IDs → Hashes | Codon, run, and loop IDs become SHA256 hashes. |
| In practice: messages → types | Error messages become `failure_type` enum values. |
| In practice: secrets → counts | Environment 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](/0.10.0/files/concepts/codons) and [Events](/0.10.0/files/reference/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": "" },
"id_hash": ""
}
```
See [Privacy principles](#privacy-principles) and [Error tracking and scrubbing](#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": "",
"id_hash": "",
"model": "",
"prompt": { "source": "", "length_chars": "" },
"checkpointed_files": { "pattern_count": "" }
}
```
See [Events](/0.10.0/files/reference/events) for the canonical event payload schemas and [Budgets](/0.10.0/files/concepts/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";
```
| Category | Names | Emitter status |
| --------------------- | ------------------------------------------------------------------- | ------------------------------------- |
| CLI | `cli_init`, `cli_validate`, `cli_cleanup`, `cli_run`, `cli_help` | Emitted |
| Run lifecycle | `run_started`, `run_completed`, `run_failed`, `run_crashed` | Emitted |
| Codon lifecycle | `codon_started`, `codon_completed`, `codon_failed`, `codon_skipped` | Emitted |
| Loop lifecycle | `loop_iteration_started`, `loop_iteration_completed` | First declared-only; second emitted |
| Rig lifecycle | `rig_setup_completed`, `rig_setup_failed` | Emitted |
| Recovery | `checkpoint_created`, `rollback_completed`, `continuation_started` | First two emitted; last declared-only |
| Sentinel | `sentinel_triggered` | Emitted |
| Budget | `budget_set`, `budget_exceeded` | Emitted |
| PostHog LLM Analytics | `$ai_generation`, `$ai_trace`, `$ai_span` | First 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.
> **DeepDive:** `loop_iteration_started`, `continuation_started`, and `$ai_span` are valid type-union members but have no emitters in this artifact.
A compact LLM analytics payload has this shape:
```json
{
"event": "$ai_generation",
"properties": {
"$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": "",
"$ai_latency": ""
}
}
```
The canonical payload definitions are owned by [Events](/0.10.0/files/reference/events). The generated catalog below describes the server events that feed runtime collection; it is not a second list of `TelemetryEventName` values.
| id | category | journaled | sentinelRouted | payloadFields | receipts |
| ------------------------ | ---------------- | --------- | -------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| archive.completed | server-state | true | true | codonId, archivedPaths | schemas/event-schemas.ts:668, schemas/event-schemas.ts:977, schemas/event-schemas.ts:1257, hankweave-runtime.ts:5465 |
| archive.partial | server-state | true | true | codonId, archivedPaths, failedPaths | schemas/event-schemas.ts:673, schemas/event-schemas.ts:978, schemas/event-schemas.ts:1258, hankweave-runtime.ts:5450 |
| assistant.action | agentic-backbone | true | true | codonId, 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.summary | server-state | true | true | ceiling, allocation, rows, totals | schemas/event-schemas.ts:724, schemas/event-schemas.ts:979, schemas/event-schemas.ts:1267, hankweave-runtime.ts:6478 |
| checkpoint.list | server-state | true | true | runId, checkpoints, currentBranch | schemas/event-schemas.ts:637, schemas/event-schemas.ts:968, schemas/event-schemas.ts:1250, hankweave-runtime.ts:4255 |
| codon.completed | server-state | true | true | codonId, 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.extended | server-state | true | true | codonId, codonName, extensionNumber, exhaustWithPrompt, cumulativeTokens, cumulativeCost | schemas/event-schemas.ts:565, schemas/event-schemas.ts:962, schemas/event-schemas.ts:1236, hankweave-runtime.ts:2946 |
| codon.started | server-state | true | true | codonId, 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 |
| error | server-state | true | true | message, 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.updated | agentic-backbone | true | true | path, filename, content, action | schemas/event-schemas.ts:587, schemas/event-schemas.ts:988, schemas/event-schemas.ts:1240, hankweave-runtime.ts:2357, hankweave-runtime.ts:3927 |
| filetree.updated | agentic-backbone | true | true | tree | schemas/event-schemas.ts:592, schemas/event-schemas.ts:989, schemas/event-schemas.ts:1241, hankweave-runtime.ts:3954 |
| history.batch | connection-state | false | false | events, hasMore | schemas/event-schemas.ts:688, schemas/event-schemas.ts:1012, schemas/event-schemas.ts:1260, hankweave-runtime.ts:1312 |
| incomplete.codon | connection-state | false | false | codonId, codonName, message | schemas/event-schemas.ts:622, schemas/event-schemas.ts:1013, schemas/event-schemas.ts:1247 |
| info | server-state | true | true | message | schemas/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.completed | server-state | true | true | loopId, 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 |
| pong | connection-state | false | false | message, 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.output | agentic-backbone | true | true | codonId, stream, line, commandIndex | schemas/event-schemas.ts:607, schemas/event-schemas.ts:992, schemas/event-schemas.ts:1244, hankweave-runtime.ts:6109 |
| rig.setup.completed | agentic-backbone | true | true | codonId, rigType, commandCount, durationMs, createdCheckpoint | schemas/event-schemas.ts:597, schemas/event-schemas.ts:990, schemas/event-schemas.ts:1242, hankweave-runtime.ts:2160 |
| rig.setup.failed | agentic-backbone | true | true | codonId, failureType, exitCode?, commandIndex?, ignored | schemas/event-schemas.ts:602, schemas/event-schemas.ts:991, schemas/event-schemas.ts:1243, hankweave-runtime.ts:1791 |
| rollback.archiveRestore | server-state | true | true | codonId, restoredPaths, failedPaths?, status | schemas/event-schemas.ts:678, schemas/event-schemas.ts:974, schemas/event-schemas.ts:1256, hankweave-runtime.ts:5557 |
| rollback.codonCheckpoint | server-state | true | true | codonId, codonName, checkpoint, checkpointType, message | schemas/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.completed | server-state | true | true | fromRun, toRun, checkpoint, codonId, codonName, checkpointType, autoRestart | schemas/event-schemas.ts:662, schemas/event-schemas.ts:972, schemas/event-schemas.ts:1255, hankweave-runtime.ts:4722, hankweave-runtime.ts:5254 |
| rollback.progress | server-state | true | true | currentStep, totalSteps, message | schemas/event-schemas.ts:657, schemas/event-schemas.ts:970, schemas/event-schemas.ts:1254, hankweave-runtime.ts:5117, hankweave-runtime.ts:5156 |
| rollback.rigCleanup | server-state | true | true | codonId, 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.started | server-state | true | true | fromRun, fromCodon, toCodon, toCheckpoint, checkpointType, codonsToProcess | schemas/event-schemas.ts:642, schemas/event-schemas.ts:969, schemas/event-schemas.ts:1251, hankweave-runtime.ts:4645, hankweave-runtime.ts:5095 |
| sentinel.error | sentinel | true | false | sentinelId, codonId, errorType, message, retriable, consecutiveFailureCount | schemas/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.loaded | sentinel | true | false | sentinelId, 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.output | sentinel | true | false | sentinelId, codonId, triggerNumber, outputType, content, cost, tokens, eventCount | schemas/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.triggered | sentinel | true | false | sentinelId, codonId, triggerNumber, strategy, eventCount, queueSize | schemas/event-schemas.ts:719, schemas/event-schemas.ts:1003, schemas/event-schemas.ts:1266, sentinels/sentinel.ts:392 |
| sentinel.unloaded | sentinel | true | false | sentinelId, codonId, reason, errorType?, finalCost, llmCallCount | schemas/event-schemas.ts:704, schemas/event-schemas.ts:1000, schemas/event-schemas.ts:1263, sentinels/sentinel-manager.ts:851 |
| server.idle | server-state | true | true | reason, message | schemas/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.ready | connection-state | false | false | serverVersion, 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.snapshot | server-state | true | true | currentCodon?, completedCodons, fileTree, totalCost, totalTime, recentFileAccess?, isRollingBack | schemas/event-schemas.ts:550, schemas/event-schemas.ts:963, schemas/event-schemas.ts:1233, hankweave-runtime.ts:1422 |
| state.transition | server-state | true | true | transitionType, runId?, codonId?, transition, resultingState | schemas/event-schemas.ts:693, schemas/event-schemas.ts:975, schemas/event-schemas.ts:1261, hankweave-runtime.ts:394 |
| token.usage | server-state | true | true | codonId, 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.result | agentic-backbone | true | true | codonId, toolUseId, toolName, result, truncated, originalLength, executionTimeMs, isError | schemas/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.
```text
HANKWEAVE_TELEMETRY_ENDPOINT= bunx hankweave@0.10.0
```
See [Troubleshooting](/0.10.0/files/operate/troubleshooting) for operational symptoms and [Environment variables](/0.10.0/files/reference/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] : { ... }`, 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.
```sh
HANKWEAVE_TELEMETRY_DEBUG=1 bunx hankweave@0.10.0
```
The observable line shape is:
```text
[TELEMETRY DEBUG] : { ... }
```
See [Observe and debug](/0.10.0/files/operate/observe-and-debug) and [Environment variables](/0.10.0/files/reference/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](/0.10.0/files/start/first-run) and [Identity and reset](#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: "",
runStatus: "",
errorCode: "",
runIdHash: "",
codonIdHash: ""
});
```
See [Troubleshooting](/0.10.0/files/operate/troubleshooting) and [Errors and exit codes](/0.10.0/files/reference/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:
```text
// 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](/0.10.0/files/contribute/runtime-architecture), [Events](/0.10.0/files/reference/events), and [Hankweave JSON](/0.10.0/files/reference/hankweave-json).