Skip to content

Privacy and analytics

Kangentic collects anonymous usage analytics, and crash and error reports, to help improve the product. No code or task content is ever collected.

Two vendors do two jobs, and both answer to the same kill switch: Aptabase counts product events, and Sentry groups and symbolicates errors so a crash is diagnosable rather than merely counted.

Usage analytics are powered by Aptabase, a privacy-first, open-source analytics platform designed for desktop apps.

EventWhen it firesProperties
app_launchApp startsPlatform, architecture. From the second launch on, it also reports the previous run’s duration, a bucketed version of it, and how that run ended (see below)
settings_snapshotOnce per app run, right after app_launchOne key per global setting that differs from its default, drawn from a fixed allowlist, plus a bucketed count of how many differ (see below)
app_heartbeatEvery 55 minutes while at least one agent session is active; skipped when idle. Also fires once right before system sleepActive, suspended, queued, and total session counts
app_errorUncaught exception, unhandled rejection, renderer crash, UI error boundary, updater failure, a failed terminal spawn, or a startup faultSource, sanitized message, plus per-source extras (see below)
project_createNew project created, through the New Project dialog or by adding a folder(none)
project_relocateProject re-pointed at another folder, through the Locate Folder dialog or the one-step Move in Project SettingsWhich of the two modes it was (repoint or move)
session_spawnAgent session reaches running state, on the board or in a Command TerminalAgent, whether the session is transient, the resolved permission mode, whether it ran in a git worktree
task_completeTask moves to DoneAgent, model, permission mode, duration, cost (USD), input/output tokens, tool calls
session_exitAgent session finishesExit code, duration, agent, model, cost (USD), tool calls, and whether the exit was intentional (a deliberate kill or suspend) rather than the agent stopping on its own
onboarding_milestoneOnce per install, per funnel stepWhich step: first project, first task, first spawn, first completed task; plus clientId
feature_first_useThe first time you touch one of eighteen curated features, once per installWhich feature, plus clientId
feature_usedOnce per curated feature per UTC dayWhich feature
board_snapshotOnce per project per app run, the first time you actually view that project’s boardColumn count, whether the columns differ from the seeded default, a bucketed task count, number of board profiles
update_outcomeNext launch after the app version changedApplied or rolled back, and the from/to versions
spawn_failedAn agent session failed to startAgent, and which path failed (create, auto-spawn, resume, unknown agent, CLI not found)
utility_worker_crashedOne of Kangentic’s own helper processes (the embeddings, line-count, dictation, search, or terminal-host worker) failed unexpectedly, as opposed to an idle recycle or a quit: it exited, could not be started, never said it was ready, or stopped answeringWhich service, the raw exit code (-1 when there is none), which of those four failures it was, and whether this was the run’s first crash or the one that gave up. At most two per service per app run
gpu_process_goneThe GPU process failed. Since v0.44.0 a kill, running out of memory, or the exit Windows forces while it shuts down does not countElectron’s reason for the exit, the exit code, and whether this was the run’s first fault or the third inside five minutes. At most two per app run
leftover_processesA task reached Done or To Do or was deleted, and its agent had left something running in its folder (see Worktree lifecycle). Never sent when a task left nothingCounts of processes stopped, kept running, and still running after the force kill, and whether stopping is switched on. Never a process name
mobile_bridge_forced_redialThe mobile bridge abandoned a relay socket that still read connected but had carried nothing for the whole presence check, the shape a router restart leaves behindWhich of the two grounds triggered it. At most once per ground per app run

The costUsd, token, and tool-call properties are cumulative session metrics, omitted when not yet available - they are counts and totals only, never any code or content.

Kangentic sends nothing from the quit path. Every way of closing the app exits before a network request can finish, so a close event fired on every quit and landed on none of them. What replaces it is a duration and a label, both reported by the next launch: while the app runs it checkpoints its own uptime to a small file in your config directory once a minute, and app_launch reports that as the previous run’s length in seconds, a bucket (<1m, 1-5m, 5-30m, 30m-2h, 2-8h, 8h+), and how the run ended - clean if the normal quit path ran (including the restart that changing Graphics acceleration asks for), failsafe if that path hung and the app force-killed itself, abrupt if nothing was recorded at all, which is what a crash, a kill, or a power loss looks like. Uptime is wall-clock and includes time the machine spent asleep. It says how long the app was open, never what you did in it.

settings_snapshot answers “what are people actually running with”, which is the question that decides whether a default is right. It fires once per app run and carries only the settings that differ from their default, so an install on stock settings sends the event with a deviation count of zero and no setting keys at all.

It can read a fixed allowlist of fifteen global settings, and nothing else:

  • Knowledge Graph: knowledgeGraph.indexingEnabled, knowledgeGraph.enabled, knowledgeGraph.localModel, knowledgeGraph.acceleration (named memory.* through v0.43.2). The Knowledge Graph’s agent and model choices are not on the list
  • Agent sessions: agent.maxConcurrentSessions (bucketed), agent.queueOverflow, agent.autoResumeSessionsOnRestart, agent.idleTimeoutMinutes (bucketed)
  • Agent Browser: browserAutomation.enabled, browserAutomation.allowEval, browserAutomation.restrictNavigationToLocalhost
  • Mobile and voice: mobileBridge.relayMode, dictation.language
  • Interface: windowLightDismiss, animationsEnabled

The allowlist is a security boundary before it is a budget one. Your global configuration also holds server URLs and credentials, relay URLs, CLI paths, init scripts, and keyboard shortcuts, and the snapshot cannot reach any of them: it reads only a listed key, and a listed key can only leave through a closed shape - a boolean, an enum drawn from a fixed set, a bucketed number, or a short pattern-checked string. A stored value that does not fit its shape is sent as the literal other, never verbatim. Project-scoped settings are out entirely, as are cosmetic settings and anything already carried by another event.

The curated feature vocabulary is eighteen entries: command terminal, worktree session, board profile, pop-out window, browser pane, MCP server, mobile bridge, usage dashboard, Quick Find, settings, Agent Monitor, semantic memory, dictation, backlog, Changes panel, conversation viewer, board integration, and pull request. Each contributes at most one feature_used event per day, and the event says only that the feature was touched - never what you used it for. Renderer-reported features cross a single IPC channel and are re-validated against that list in the main process, so a drifted renderer cannot invent new event names.

board_snapshot sends counts only. Column names and task content never leave the machine, and the task count is bucketed (0, 1-9, 10-49, 50-199, 200+) rather than sent exactly.

An app_error’s source says which failure path it came from: uncaughtException, unhandledRejection, render-process-gone (adding the crash reason and exit code), error_boundary (adding which of the three reporters caught it and the failing panel’s label), updater, pty_spawn and pty_spawn_cwd_missing (adding the shell, whether the shell and working directory exist, the errno, and the platform), and five startup and window-lifecycle faults: secondInstanceNoWindow, duplicateCreateWindow, createWindowBeforeMcpSettled, globalDbUnreadable, and startupFailure. The first three of those five carry a fixed sentence and no data at all; the last two carry a sanitized error message.

A UI error says where it happened without carrying a stack: which of the three reporters caught it, and the failing panel’s static label. The React component stack never leaves the renderer process. A production stack frame embeds a file:// URL containing your home directory, and the earlier compromise of reducing it to bare component names produced a mangled trail from the minified bundle, so that property was dropped rather than kept. Sentry receives the real, symbolicated stack instead, with paths normalized first - see Crash and Error Reports.

The Aptabase SDK also records: OS name, OS version, locale, app version, and country (derived from IP, then immediately discarded).

Three of those events carry clientId, a non-reversible id Kangentic derives from your OS install (an HMAC-SHA256 digest of already-hashed machine and home-directory identifiers, keyed with a fixed salt). It rides app_launch, the one authoritative per-launch install signal, and the two lifetime-once events, feature_first_use and onboarding_milestone, where it turns “this many first uses” into “this many installs reached that step” - each of those fires at most once per install for all time. It is deliberately not attached to every event, so a high-cardinality string does not ride along on app_heartbeat or the daily feature_used, where it would add nothing to an install count. It contains no raw machine identifiers, paths, or usernames.

It is, however, stable: the same value across app updates, and unchanged by an uninstall and reinstall. It is unique per OS user account. A stable identifier of that kind is ordinarily personal data under the GDPR, so it is described here rather than filed under “nothing personal”. It shares the same KANGENTIC_TELEMETRY control as every other event - there is no separate opt-out.

Crash monitoring is separate from product analytics and goes to Sentry. Aptabase’s app_error stays a coarse error-rate pulse; Sentry is what turns a crash into something diagnosable, with grouping, deduplication, and a readable stack trace instead of a truncated string.

  • Paths are normalized before anything leaves your machine. The Sentry SDK rewrites stack-frame paths and URLs relative to the app root, so your home directory does not travel with a stack trace. Since v0.44.0 the app also rewrites your home directory to ~ in every string of an event before it is sent, because an exception message is free text that path normalization never touches, and an OS username is often a real name. Personally identifiable data collection (sendDefaultPii) stays off, and Sentry’s own server-side data scrubbing is on.

  • A native crash’s stack is built on Sentry’s side, and scrubbed there. Sentry reads frames, the module list, and the crash reporter’s notes out of the uploaded dump on its servers, where the app’s rewrite cannot reach. A Sentry-side rule replaces home-directory paths in those fields, applied 2026-10-03. One field survives it: a frame’s package, the app’s own install path, which on a per-user install sits under your home directory. Sentry stores no IP address for a report, but keeps the city it derives from one.

  • Errors only. Release-health session tracking is filtered out, and performance tracing and session replay are never enabled.

  • The renderer has no network path of its own. Renderer errors travel to the main process over internal IPC, and the main process is the single point of egress - the same shape as the analytics funnel.

  • A Browser pane page’s crash arrives without the page. A page you load in the Browser pane runs inside Kangentic’s own process, so its crash dump reads as the app’s. Since v0.44.0 such a crash becomes one warning, “A Browser pane page crashed”, with the page’s address, its stack, the crash reporter’s notes, and the dump removed. The exit reason and the app version stay, so the rate of page crashes is still visible.

  • Errors that used to be counted are now reported. Deliberate catch sites that previously emitted only a sanitized count (updater failures, terminal spawn failures, silent agent-spawn catches) send the real error, so a hidden problem is fixable rather than just visible as a number. A failed cleanup of a task’s leftover processes joined them in v0.44.0, as a fixed code with no process name or text. A settings write that could not reach disk joined them in v0.43.0, at one report per failing file per outage. A full disk is included on purpose even though it is a host condition rather than a defect: it is the only evidence that the toast telling you about it fires at all, and the per-file latch already caps the volume.

  • A missing agent CLI is never reported. It is your environment rather than a defect, so the spawn-blocked toast in the app is the whole response. spawn_failed still counts it, so how often it happens stays answerable.

  • Transient failures are counted, not reported. An update check that fails on a network hiccup or a GitHub 504, and a helper-process crash the app recovers from by restarting it, each increment an Aptabase counter without opening a Sentry issue. Only a helper that crashes past its restart cap is reported, and only once.

  • A declined Linux update is counted, not reported. Dismissing the elevation prompt on a .deb or .rpm update is your own choice rather than a defect, so it increments app_error without opening a Sentry issue. An install that actually ran and failed still reports. The two are told apart by the exit code pkexec uses for a dismissed dialog, which no package manager returns, so the distinction holds on the current GNOME and KDE desktops that use it; an older front-end exits the same way whether you cancelled or the install broke, and a cancel there is still reported.

  • A reported helper crash carries the last 8 KiB of that helper’s own error output, because an exit code alone could never say why it died. This is the one place a report carries free text rather than a stack trace, so it gets its own treatment: your home directory is replaced with ~ in that text before it goes anywhere. The same text is written to the app’s local log either way, whether or not error reporting is on. Since v0.45.0 the report also lists each crash in the restart window, one fixed-format line apiece: how the helper failed, its exit code when it had one, and, for a helper that stopped answering, the names of the internal requests it had not answered, such as projects.summaries. Those are Kangentic’s own method names, never your content.

  • Native crashes are uploaded, and checked for ownership first. A crash inside native code writes a crash dump. A child process that dies while the app is running is reported there and then; a crash that took the whole app down is found and uploaded on the next launch. A dump of Kangentic’s own is attributed to the app version that actually crashed rather than the one that uploaded it.

    On macOS a program Kangentic starts can inherit its crash handler and write its crash into Kangentic’s dump directory. Since v0.43.2 the macOS app clears that handler before it runs a terminal’s program and before its shell launches: the login-shell probe, shortcuts, run-script automations, and worktree init scripts. The ownership check stays as the backstop. Each dump is read for its list of loaded libraries, and one that holds none of Kangentic’s code never uploads, because it holds another program’s memory. What goes instead is one warning saying a foreign program crashed, with the dump, the breadcrumb trail, and the crash reporter’s notes about that program removed. The warning names the program only when it ran from a location that installers and package managers use, such as a system directory, Homebrew, a per-user toolchain directory like ~/.cargo/bin, or a node_modules folder. Anything else reads user-binary, so the name of your own build output stays on your machine. One gap is accepted. A program of your own that cargo install, go install, or make install put in one of those locations keeps its name. The check fails open on purpose. A dump it cannot read is sent as Kangentic’s own, so a bug in the reader can never silently delete a real crash.

  • Breadcrumbs are filtered on your machine before they are recorded. Every report carries a trail of what the app did just before it, up to 100 entries from the main process and 100 from the window. Since v0.43.2 each entry passes a filter in the process that records it. A console line is dropped unless it carries one of a short list of diagnostic tags (updates, shutdown, terminal rendering, graphics, and the app’s own uncaught errors and startup faults) or is Electron’s own line for a failed internal call. A kept line is rebuilt with paths redacted and each error reduced to its name and code. A click keeps the element’s selector and loses its title and label text. A network request keeps its method and status, and its address only when it is Kangentic’s own (the GitHub repository, release downloads, and the analytics endpoint), without the query. When a program the app started fails to launch or exits with an error, its entry keeps the program’s file name and drops the folder it ran from. An app lifecycle entry, such as a window gaining focus, keeps its name. Page navigations and every kind of entry the filter does not know are dropped. The filter fails closed, so an entry it cannot judge is dropped.

  • A GPU crash is reported once, on the next launch. Every death now writes a small record to the config directory, and the following launch reports it with the sequence of graphics modes the deaths happened on, the GPU’s feature status then and now, and how the previous run ended, then clears the record. Next-launch rather than live because the failure that matters ends in Chromium killing the whole app, which happens before a live report could send. Writing every death rather than only the third is what makes that work: Chromium takes the app down from a code path that runs before the app is told anything, so a record gated on a count could lose the whole incident.

    Since v0.43.2 a fallback from GPU rendering writes the record too. A graphics process that cannot be started again reports no death at all, so the fallback is the only trace that failure leaves. On Linux the record also carries whether the process Chromium starts the graphics process from was still alive, the system-wide thread count, and the per-user process limit. Those three tell a dead launcher apart from a machine at its process limit, which fail the same way.

    What is reported is narrower than what is written. A record reaches Sentry when the deaths cross Chromium’s own three-in-five-minutes judgment, or when a death or a fallback sat right at the end of a run that ended abruptly. A lone GPU death the app recovered from is counted and not reported, and a fallback the run survived is not reported either. That second condition is also what puts the next launch into software rendering and tells you so, and that notice does not depend on error reporting being on: someone who turned telemetry off is exactly the person who would otherwise get no explanation.

  • Every report carries a host-memory snapshot. The main process samples system memory every 60 seconds and attaches the freshest sample to whatever report fires next, native crashes included, because an out-of-memory crash is otherwise undiagnosable from the event alone. Physical memory total and free, plus the commit limit and how much remains on Windows, and since v0.44.0 the kernel’s available-memory figure on Linux; nothing about what is using it.

  • One diff-viewer error carries the viewer’s shape. A known error inside the diff editor (“Illegal value for lineNumber”) is reported as handled since v0.44.0, with each open diff viewer’s line counts, scroll position, visible line ranges, and fold state, plus the file names (never the folders) of the code that called it. No file path or file content is sent, and at most one such report goes out per 30 seconds.

  • Affected-install counts use the same clientId described above, attached as the Sentry user id so an issue’s user count means “installs affected”. It carries no personal data and shares the same opt-outs.

  • Source maps never ship. They are uploaded at release time and deleted from the build output, so stack traces resolve on Sentry’s side rather than from anything in the app you installed. The Windows build also uploads debug symbols for the terminal’s native module, so a native crash symbolicates on Sentry’s side too.

  • Task titles, descriptions, or any user-generated content
  • Project names or code
  • File paths: analytics events never carry one, and stack-frame paths in an error report are normalized to the app root before it is sent. Your home directory is replaced with ~ in a helper process’s error output at the point it is captured and in every other string of an event before it leaves, and breadcrumbs have paths redacted before they are recorded. The exception is a native crash’s frame package, described under Crash and error reports
  • Console output other than the diagnostic lines described under Crash and error reports, the text of what you click, and any address the app requests that is not Kangentic’s own
  • Usernames, emails, or personally identifiable information
  • Mid-board task moves (only completion is tracked)
  • What you did with a feature: feature_used records that a feature was touched on a given day, never the content of that use

KANGENTIC_TELEMETRY is the superset switch - setting it to 0 disables usage analytics and error reporting. KANGENTIC_ERROR_REPORTING controls error reporting alone.

VariableValueBehavior
KANGENTIC_TELEMETRY0 or falseAll telemetry disabled: analytics and error reporting
KANGENTIC_TELEMETRY1 or trueTelemetry force-enabled, even in dev builds (useful for local testing)
KANGENTIC_TELEMETRY(unset)Enabled in production builds, disabled in development
KANGENTIC_ERROR_REPORTING0 or falseError reporting disabled; usage analytics unaffected
KANGENTIC_ERROR_REPORTING1 or trueError reporting enabled even in dev builds, unless KANGENTIC_TELEMETRY=0
KANGENTIC_ERROR_REPORTING(unset)Follows whatever KANGENTIC_TELEMETRY resolves to

Open Settings > Privacy to see a summary of what is and isn’t collected. The Privacy tab is informational - opt-out is controlled exclusively by the environment variables above. It also points to the Knowledge Graph tab for the conversation-search controls described below.

Settings on the Privacy tab: what anonymous analytics are collected and what is not, how they work, and how to opt out. v0.46.0 The real app on a sample install. The projects and terminals are recordings, connected to nothing, so click anything.

Kangentic’s search index and its voice dictation process your data with local, on-device models and no API key:

  • Conversation memory indexes your agents’ past conversations, each task’s own record, and the default branch’s commits, so you can search and recall them. Indexing is fully local: only the structured conversation turns are indexed (never the raw terminal scrollback), the index lives in each project’s database, and searching never leaves your machine. Indexing is on by default and can be turned off. The Knowledge Graph that adds search by meaning is opt-in, and the index’s one network access is the download of a local embedding model (from Hugging Face) the first time you turn it on - after that it runs offline. It downloads again only when you pick another model, or when an update replaces the one you run. Manage both under Settings > Knowledge Graph.
  • Dictation transcribes push-to-talk voice to text on-device by default (an optional cloud endpoint can be configured for the final refinement pass only; the live preview always stays local). Its models download when you turn dictation on, and again when you pick other ones or an update changes the models a preset runs.

Neither sends your code, task content, or conversations to Kangentic or any analytics backend.

The Knowledge Graph’s Ask and its task summaries are the exception to “local”, by design. They send text to the agent you choose for them in Settings, the same way any task you give that agent does: a question sends the related tasks’ passages, facts, and summaries, a table of the tasks in scope, and, with Source code on, the closest code passages; a summary call sends each task’s title, description, changed files, and commits. Where that text goes depends on the agent and model you pick. Neither runs until you pick one. See Knowledge Graph privacy.

  • All telemetry leaves from the main process - the renderer never opens a network path of its own, for analytics or for error reports
  • Error messages are sanitized to strip file paths before transmission, and error-report stack frames are rewritten relative to the app root
  • Aptabase uses no cookies, and its own session IDs are random and rotate daily. Kangentic separately attaches a non-reversible clientId (see above) so it can count unique installs; it is not an Aptabase feature
  • The analytics request reveals your IP address to Aptabase, which resolves it to a country and then discards the address
  • All analytics calls are fire-and-forget and never block the app
  • Analytics retention follows Aptabase’s privacy policy; error reports follow Sentry’s plan retention for the Kangentic organization

Kangentic stores your task, session, and conversation data locally on your own machine, so most data requests are things you can act on yourself. A project’s tasks, sessions, board history, and conversation index live in its database in Kangentic’s data directory, not in the repository (see Storage). The project’s .kangentic/ directory holds its settings, worktrees, session files, attachments, and logs; the data directory keeps a log only while no project is open (see Finding log files). Deleting the project in Kangentic removes both its database and its .kangentic/ directory, and also removes the kangentic.json board file from the repository. Global settings live in the data directory too, listed under Configuration.

The analytics and error reports described above are the only data the app reports back. It also downloads its update feed and the announcements feed from GitHub, and neither request carries an identifier. Everything else that leaves your machine goes where you point it, such as your git remotes or the agent you choose; the Privacy Policy lists those under Optional features that use the network.

If you have a question about this policy, or a request that needs a human, email support@kangentic.com.

  • Privacy Policy - the formal policy covering desktop, mobile, and this website
  • Settings - the in-app Privacy tab summarizing collection
  • Auto-updates - what’s checked when the app polls for new versions

Kangentic is free and open source. A star helps other people find it.

Star on GitHub