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 is written only by the interactive TUI, at the first successful run it shows, where it powers the star nudge; a --headless run never builds the TUI, so headless and CI runs never set the field.
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:
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.
/**
* Resolve telemetry configuration from all sources.
*
* @param fileConfig - Configuration from hankweave.json telemetry section
* @returns Resolved telemetry configuration
*/
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. The published editor schema gives the object additionalProperties: false, so an editor or JSON Schema validator flags an unknown key – but the runtime does not reject it: the loader nests a plain schema for telemetry that strips unknown keys, so the rest of the file still loads and the unknown key is dropped. Unknown top-level keys are different: they fail the strict load, and resolveSettings() then silently drops the whole file layer.
| field | type | default | required | constraints | description |
|---|---|---|---|---|---|
telemetry | object | no | Telemetry 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(), so a missing, non-regular or unparsable file yields undefined and never blocks telemetry resolution. resolveSettings() itself does not strip telemetry – it deep-merges the parsed object into the effective config; a source comment claiming otherwise is stale.
For the highest-priority opt-out, run:
DO_NOT_TRACK=1 bunx hankweave@0.11.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:
/**
* Telemetry Types and Schemas
*
* Privacy-preserving types for telemetry events.
* All types follow the principle: content → size, paths → counts, IDs → hashes.
*/
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 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, narrower scrubber. Before an error is sent, captureError() redacts a fixed set of shapes from the error message and stack trace: home paths under /Users, /home and the Windows user tree; tokens prefixed sk-, key-, token- or Bearer (10+ characters); NAME=value only for API_KEY, SECRET, TOKEN, PASSWORD, ANTHROPIC_API_KEY, OPENAI_API_KEY and GEMINI_API_KEY; and absolute paths only when they end in .json, .md, .txt, .yaml or .yml. Other paths and secret shapes pass through unchanged, and the scrubber mutates the error's .message and .stack in place. Exceptions PostHog auto-captures never pass through it.
The result is that a prompt appears as a measurement rather than its contents:
{
"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; is_ci is hard-coded false in the collector and is never set from the CI detector.
A collected codon therefore uses measurements and labels instead of source content:
{
"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 28 names in nine categories. Twenty-five have emitters; loop_iteration_started, continuation_started, and $ai_span are declared-only. The union and the per-category status table below show both:
export type TelemetryEventName =
// CLI events
| "cli_init"
| "cli_validate"
| "cli_cleanup"
| "cli_run"
| "cli_help"
| "cli_restore_journal"
| "cli_diet_journal"
// 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, cli_restore_journal, cli_diet_journal | 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, cli_restore_journal, cli_diet_journal) are sent through sendCliTelemetry(), which creates a temporary collector and awaits the event's flush and then the client shutdown before exiting. The normal cli_run event is sent the same awaited way by the main collector before the server starts. Both steps race a two-second timeout and swallow errors, so telemetry never throws – but because they are awaited, a slow or unreachable backend can delay an early-exit command by about four seconds and run startup by up to two seconds. Codon-lifecycle events, together with the other accumulated events (budget, loop, rig, recovery, sentinel, $ai_generation, and run_crashed), are queued from the server event stream and batch-sent at shutdown. run_started, run_completed and run_failed are not queued – they are composed from the final run state at shutdown.
$ai_generation is emitted once per completed codon, or once per model when the codon's modelUsage is present; 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:
{
"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. Its ? marks reflect each event's base schema: for assistant.action, toolName and toolUseId are required when action is "tool_use" (toolUseId must be a non-empty string; it joins the action to tool.result and file.updated.source), while thinking and message actions may omit both.
| id | category | journaled | sentinelRouted | payloadFields | receipts |
|---|---|---|---|---|---|
| archive.completed | server-state | true | true | codonId, archivedPaths | schemas/event-schemas.ts:780, schemas/event-schemas.ts:1091, schemas/event-schemas.ts:1371, hankweave-runtime.ts:5784 |
| archive.partial | server-state | true | true | codonId, archivedPaths, failedPaths | schemas/event-schemas.ts:785, schemas/event-schemas.ts:1092, schemas/event-schemas.ts:1372, hankweave-runtime.ts:5769 |
| assistant.action | agentic-backbone | true | true | codonId, action, content, toolName?, toolInput?, toolUseId? | schemas/event-schemas.ts:684, schemas/event-schemas.ts:1100, schemas/event-schemas.ts:1351, hankweave-runtime.ts:3210 |
| budget.summary | server-state | true | true | ceiling, allocation, rows, totals | schemas/event-schemas.ts:836, schemas/event-schemas.ts:1093, schemas/event-schemas.ts:1381, hankweave-runtime.ts:7086 |
| checkpoint.list | server-state | true | true | runId, checkpoints, currentBranch | schemas/event-schemas.ts:749, schemas/event-schemas.ts:1082, schemas/event-schemas.ts:1364, hankweave-runtime.ts:4612 |
| codon.completed | server-state | true | true | codonId, success, cost, duration, exitStatus, failureReason?, failureIgnored?, budgetExceeded? | schemas/event-schemas.ts:672, schemas/event-schemas.ts:1075, schemas/event-schemas.ts:1349, hankweave-runtime.ts:3792 |
| codon.extended | server-state | true | true | codonId, codonName, extensionNumber, exhaustWithPrompt, cumulativeTokens, cumulativeCost | schemas/event-schemas.ts:677, schemas/event-schemas.ts:1076, schemas/event-schemas.ts:1350, hankweave-runtime.ts:3378 |
| codon.started | server-state | true | true | codonId, codonName, codonDescription?, sessionId, previousSessionId?, startTime, promptMetadata? | schemas/event-schemas.ts:667, schemas/event-schemas.ts:1074, schemas/event-schemas.ts:1348, hankweave-runtime.ts:385 |
| error | server-state | true | true | message, codon?, fatal, severity?, context?, code? | schemas/event-schemas.ts:729, schemas/event-schemas.ts:1081, schemas/event-schemas.ts:1360, hankweave-runtime.ts:1214 |
| file.updated | agentic-backbone | true | true | path, filename, action, sha256, bytes, source | schemas/event-schemas.ts:699, schemas/event-schemas.ts:1102, schemas/event-schemas.ts:1354, hankweave-runtime.ts:3027 |
| filetree.updated | agentic-backbone | true | true | tree | schemas/event-schemas.ts:704, schemas/event-schemas.ts:1103, schemas/event-schemas.ts:1355, hankweave-runtime.ts:3039 |
| history.batch | connection-state | false | false | events, hasMore | schemas/event-schemas.ts:800, schemas/event-schemas.ts:1126, schemas/event-schemas.ts:1374, hankweave-runtime.ts:1563 |
| incomplete.codon | connection-state | false | false | codonId, codonName, message | schemas/event-schemas.ts:734, schemas/event-schemas.ts:1127, schemas/event-schemas.ts:1361 |
| info | server-state | true | true | message | schemas/event-schemas.ts:739, schemas/event-schemas.ts:1080, schemas/event-schemas.ts:1362, hankweave-runtime.ts:2102 |
| loop.iteration.completed | server-state | true | true | loopId, iteration, durationMs, costUsd, tokensUsed, isFinal, terminationReason? | schemas/event-schemas.ts:724, schemas/event-schemas.ts:1090, schemas/event-schemas.ts:1359, hankweave-runtime.ts:6415 |
| pong | connection-state | false | false | message, timestamp, clientId? | schemas/event-schemas.ts:795, schemas/event-schemas.ts:1125, schemas/event-schemas.ts:1373, hankweave-runtime.ts:1466 |
| rig.output | agentic-backbone | true | true | codonId, stream, line, commandIndex | schemas/event-schemas.ts:719, schemas/event-schemas.ts:1106, schemas/event-schemas.ts:1358, hankweave-runtime.ts:6489 |
| rig.setup.completed | agentic-backbone | true | true | codonId, rigType, commandCount, durationMs, createdCheckpoint | schemas/event-schemas.ts:709, schemas/event-schemas.ts:1104, schemas/event-schemas.ts:1356, hankweave-runtime.ts:2604 |
| rig.setup.failed | agentic-backbone | true | true | codonId, failureType, exitCode?, commandIndex?, ignored | schemas/event-schemas.ts:714, schemas/event-schemas.ts:1105, schemas/event-schemas.ts:1357, hankweave-runtime.ts:2213 |
| rollback.archiveRestore | server-state | true | true | codonId, restoredPaths, failedPaths?, status | schemas/event-schemas.ts:790, schemas/event-schemas.ts:1088, schemas/event-schemas.ts:1370, hankweave-runtime.ts:5925 |
| rollback.codonCheckpoint | server-state | true | true | codonId, codonName, checkpoint, checkpointType, message | schemas/event-schemas.ts:759, schemas/event-schemas.ts:1085, schemas/event-schemas.ts:1366, hankweave-runtime.ts:5048 |
| rollback.completed | server-state | true | true | fromRun, toRun, checkpoint, codonId, codonName, checkpointType, autoRestart | schemas/event-schemas.ts:774, schemas/event-schemas.ts:1086, schemas/event-schemas.ts:1369, hankweave-runtime.ts:5075 |
| rollback.progress | server-state | true | true | currentStep, totalSteps, message | schemas/event-schemas.ts:769, schemas/event-schemas.ts:1084, schemas/event-schemas.ts:1368, hankweave-runtime.ts:5469 |
| rollback.rigCleanup | server-state | true | true | codonId, codonName, directories, status, successfulCleanups?, failedCleanups?, error? | schemas/event-schemas.ts:764, schemas/event-schemas.ts:1087, schemas/event-schemas.ts:1367, hankweave-runtime.ts:5998 |
| rollback.started | server-state | true | true | fromRun, fromCodon, toCodon, toCheckpoint, checkpointType, codonsToProcess | schemas/event-schemas.ts:754, schemas/event-schemas.ts:1083, schemas/event-schemas.ts:1365, hankweave-runtime.ts:5010 |
| sentinel.error | sentinel | true | false | sentinelId, codonId, errorType, message, retriable, consecutiveFailureCount | schemas/event-schemas.ts:821, schemas/event-schemas.ts:1115, schemas/event-schemas.ts:1378, sentinels/sentinel.ts:576 |
| sentinel.loaded | sentinel | true | false | sentinelId, codonId, model, triggerType, executionStrategy, conversational, source, sourcePath? | schemas/event-schemas.ts:811, schemas/event-schemas.ts:1113, schemas/event-schemas.ts:1376, hankweave-runtime.ts:6201 |
| sentinel.output | sentinel | true | false | sentinelId, codonId, triggerNumber, outputType, content, cost, tokens, eventCount | schemas/event-schemas.ts:826, schemas/event-schemas.ts:1116, schemas/event-schemas.ts:1379, sentinels/sentinel.ts:538 |
| sentinel.triggered | sentinel | true | false | sentinelId, codonId, triggerNumber, strategy, eventCount, queueSize | schemas/event-schemas.ts:831, schemas/event-schemas.ts:1117, schemas/event-schemas.ts:1380, sentinels/sentinel.ts:392 |
| sentinel.unloaded | sentinel | true | false | sentinelId, codonId, reason, errorType?, finalCost, llmCallCount | schemas/event-schemas.ts:816, schemas/event-schemas.ts:1114, schemas/event-schemas.ts:1377, sentinels/sentinel-manager.ts:908 |
| server.idle | server-state | true | true | reason, message | schemas/event-schemas.ts:744, schemas/event-schemas.ts:1078, schemas/event-schemas.ts:1363, hankweave-runtime.ts:1180 |
| server.ready | connection-state | false | false | serverVersion, executionPath, agentRootPath, dataPath, port, proxyPort?, outputDirectory? | schemas/event-schemas.ts:657, schemas/event-schemas.ts:1124, schemas/event-schemas.ts:1346, hankweave-runtime.ts:1155 |
| state.snapshot | server-state | true | true | currentCodon?, completedCodons, fileTree, totalCost, totalTime, recentFileAccess?, isRollingBack | schemas/event-schemas.ts:662, schemas/event-schemas.ts:1077, schemas/event-schemas.ts:1347, hankweave-runtime.ts:1676 |
| state.transition | server-state | true | true | transitionType, runId?, codonId?, transition, resultingState | schemas/event-schemas.ts:805, schemas/event-schemas.ts:1089, schemas/event-schemas.ts:1375, hankweave-runtime.ts:463 |
| token.usage | server-state | true | true | codonId, inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens, totalCost, modelId?, modelUsage? | schemas/event-schemas.ts:689, schemas/event-schemas.ts:1079, schemas/event-schemas.ts:1352, hankweave-runtime.ts:3075 |
| tool.result | agentic-backbone | true | true | codonId, toolUseId, toolName, result, truncated, originalLength, executionTimeMs, isError | schemas/event-schemas.ts:694, schemas/event-schemas.ts:1101, schemas/event-schemas.ts:1353, hankweave-runtime.ts:3338 |
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.
Every send is timeout-bounded and silent: capture(), captureMany() and shutdown() each race their flush against a two-second timeout and swallow every error, so telemetry never throws to the user even when the backend is slow or unreachable. The PostHog client uses flushAt: 1 and flushInterval: 0, so each capture() flushes immediately rather than batching; runtime events are queued and sent at shutdown with captureMany(). The CLI awaits these sends (see Event catalog and emitter status for the delay bounds).
HANKWEAVE_TELEMETRY_ENDPOINT=<endpoint-URI> bunx hankweave@0.11.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.
HANKWEAVE_TELEMETRY_DEBUG=1 bunx hankweave@0.11.0
The observable line shape is:
[TELEMETRY DEBUG] <event_name>: { ... }
See Observe and debug and Environment variables.
First-run notice#
After configuration resolution and before execution starts, a launch that reaches server startup prints a boxed notice saying that Hankweave collects anonymous usage statistics, that no personal information, file contents, or prompts are collected, that links to this page, and that names HANKWEAVE_TELEMETRY=0 as an opt-out. Headless runs print it too; early-exit commands, attach mode, and the bare-invocation wizard never reach it. The notice ignores the telemetry settings – it prints even with DO_NOT_TRACK=1, HANKWEAVE_TELEMETRY=0, or telemetry disabled in hankweave.json.
It is shown until it is marked as seen, and that mark is best-effort. hasNoticeBeenShown() checks noticeShownAt in the identity file, and markNoticeShown() records it by reading and rewriting that file, swallowing every failure. Normal startup prints the notice before it creates the identity file, so on a machine with no identity file the first launch prints the notice without recording the mark, and the notice appears again on the next launch, which records it. The notice's own line "This notice won't be shown again." is its text, not a guarantee. The source-defined text is:
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; those auto-captured exceptions are not scrubbed by Hankweave. Manual captureError() calls add failure and correlation context such as codonStatus, runStatus, errorCode, exitCode, failureType, runIdHash, and codonIdHash.
Before a manual capture is sent, the fixed-regex scrubber described under What is never collected redacts home paths, a limited set of token and NAME=value shapes, and absolute paths ending in .json, .md, .txt, .yaml or .yml; other paths and secret shapes pass through unchanged, and the scrubber 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:
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 so that a broken file never blocks it; resolveSettings() deep-merges the parsed telemetry object rather than stripping it.
The order is:
// 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.