Oct 15, 2026
Count Arkos users without identifying anyone
Arkos can learn how many machines run it, how often they come back, which features and which coding agents get used, and which releases crash and where, without ever receiving a file name, path, project name, hostname, username or persistent identifier. The evidence from Go's transparent telemetry, Chromium's Omaha updater, Homebrew and Firefox's retired install census converges on one shape: four coarse dimensions (app version, macOS major version, CPU architecture, install channel) plus a bag of named integer counters, with active-user and retention arithmetic carried by two client-reported numbers, "days since last report" and "days since last active", instead of by any ID. Crash reports shrink to a build identity, instruction addresses with the loaded-image table, a fault classification and a few environment facts, with symbolication done on the maintainer's server from archived dSYMs. Every developer tool that shipped default-on telemetry without a notice before the first send drew a backlash (Homebrew 2016, .NET 2016, Audacity 2021, Go 2023, Fedora 2023), while the projects that chose opt-in have had none; EU ePrivacy guidance treats locally generated information sent home as "gaining access" to the device, so anything beyond the bare update check needs consent or a strong necessity argument. The recommended design is Go's local default plus a one-time invitation, Omaha-style identifier-free roll calls, a typed allowlist wire format checked into the repository, per-incident crash consent that shows the exact bytes, a first-party endpoint that never writes IP addresses, published aggregates with a cell floor of ten, and a telemetry page built from the nine items that Go, Homebrew, KDE, Mozilla and GitHub CLI all publish. Its cost is a small, self-selected sample and a symbolication pipeline the maintainer must run; both are cheaper than the trust the alternatives have cost other projects.
The tools that measure usage without identifiers all arrived at the same data model. Go telemetry stores "only counter names and 64-bit totals, with no event log or trace", keyed by program, version, toolchain version, GOOS, GOARCH and week start, so every report inherently carries program, version, OS and architecture dimensions and nothing finer [1]. Its privacy page describes the collected classes as event counters, stack traces restricted to the toolchain's own functions, and "Your operating system, CPU architecture, and name and version of the Go tool being executed", and states the files "do not contain personal or other identifying information about you or your system" [2]. Homebrew's payload for install and build-error events carries the package name, non-private tap name, selected install options, whether the install was requested or pulled in as a dependency, CPU architecture, OS name or major version and Homebrew version; the install prefix is reported only as default or custom-prefix, command events carry command and option names "with option values removed", build-error events "do not include build logs or exception details", and the payload has no user identifier or IP-address field [3]. Firefox's legacy Active Daily Installs request "does not contain the telemetry client_id or any other persistent identifiers"; it carried app ID, version, build ID, locale, update channel, OS version, distribution and "Number of full days since last ping" as the only time-based signal [4].
The arithmetic that turns those fields into traction metrics needs no ID at all. Chromium's Omaha updater protocol is explicitly "anonymous, but allows servers to compute the number of unique active or update-checking devices" through client-regulated counting: the client sends ad, "The date that the previous active report took place on, or '-1' if this is the first active report", and rd, the date of the previous roll call, and the server keeps only the first request per client in the window it is measuring [5]. Firefox's census worked the same way: for each day, Mozilla added up requests whose days-since-last-ping indicated a first-ever request (a new install) or a last request before the analysis window, which yields new installs, returning installs and daily, weekly and monthly actives from one integer [4]. Go derives active-user estimates from the LastWeek field over longer periods, and produces histograms (its example is a build-cache miss-rate histogram) and breakdowns by GOOS, GOARCH, Go version and program version from counters alone [1]. Omaha even buckets the install date on the client, storing X - X % 7 so the server learns an install week, never a day, "to limit privacy risk" [5].
For Arkos this means the identifier-free report that answers "how many people, how often, do they return, which features matter" contains: app version; macOS major (or major.minor) version; CPU architecture; install channel (tap versus direct download); install-week bucket; days_since_last_report and days_since_last_active; named integer counters for feature invocations, sessions started, session-length histogram buckets, crashes and clean exits; and a CI or automation flag, which Homebrew records as a context flag so CI traffic can be separated [3]. Crash-free rate falls out of two counters per report (sessions and crashes) exactly as Go computes histograms from counter totals, and version adoption and OS distribution come from the dimensions. For a tool whose purpose is running AI coding agents, "which agent runtime was launched" is a feature counter like any other, provided the value is drawn from a fixed enum of known agents, never from the command line; both Astro and the .NET SDK now record which AI coding agent invoked them as a named category, not as a string [6], [7].
Two proxies need no client code at all, and both count downloads rather than people. GitHub's release-asset API exposes a required download_count integer with no description of what is counted and no bot or CI exclusion statement [8]. crates.io's history shows the two canonical pitfalls of download proxies in one project: when it moved counting from its API servers to CDN log analysis in March 2024 so cargo could fetch directly from the CDN, counts were expected to jump because the old method under-counted, and an October 2025 pull request proposes counting only requests whose user agent belongs to cargo "to filter out all of the systems that appear to mirror crates.io" [9], [10]. Homebrew records non-private tap names in its payload, but its public analytics API paths cover only homebrew-core and homebrew-cask, so whether a third-party tap formula's installs ever surface is unconfirmed [11], [3]. Treat download_count as a ceiling, not a user count.
The counting techniques fall on a spectrum of what they leak and what they need on the client, and the technique that fits a web page is the wrong one for an installed tool. Web analytics providers count uniques with a daily salted hash: Plausible's visitor identifier is hash(daily_salt + website_domain + ip_address + user_agent), "The salt is rotated and deleted every 24 hours", "Raw IP addresses and User-Agent data are never stored", and the result "does not let us recognize the same visitor across days, across devices or on another website" [12]. GoatCounter builds a session ID from site ID, user agent and IP, keeps it only in memory for eight hours, and stores only a random UUID [13]. The maintainer's own lib-visits uses the same family (a keyed hash of the day, the address and the browser) and states the caveat plainly: whoever holds both the secret and the events "can test whether a given address and browser visited on a given day" [14]. All three need nothing on the client, but all three admit the collector sees the IP for the salt period, and all three count within one day only. They exist because a web hit is stateless; an installed app is not.
Client-regulated counting leaks strictly less. Omaha needs only a stored date on the client, never touches the IP, and gives exact-ish unique counts: dedup must be "cr" (client-regulated), sessionid and requestid are random per session and request, the install id iid must be cleared "after transmitting it once" to avoid becoming a persistent identifier, and ping_freshness is "A random 128-bit number, written into the client's storage alongside the next value of rd, and rotated whenever the client stores a new value for rd", so that duplicate freshness values flag "a machine's state has been cloned or reset" and the dates are "not trustworthy" [5]. A testsource field marks the maintainer's own probes as traffic that "should not be counted toward official metrics" [5]. Accuracy is exact up to clock skew, reinstalls and cloned state, where a salted hash undercounts users behind a shared IP and overcounts users whose IP changes within the day. The NAT effect is not small: Mozilla once saw 98.6 million blocklist pings in a day from 61.5 million unique IPs [15].
Go adds sampling to counters. Once a week the client draws X in [0,1); if X is at least 0.1 it stops without even fetching the configuration, otherwise it uploads only counters whose configured rate is at least X, so "a typical reporting rate will be under 0.02 (2%)"; "Uploaded reports do not include user IDs, machine IDs, or any other kind of ID", IP addresses "are not recorded with the reports", and each day's uploads are published in full [1]. The statistics are explicit: "For a margin of error of 1% at a 99% confidence level, we need about 16,000 samples", and because the sample size is fixed, "as new systems are added to the system, each system reports less often" [1]. Sampling only matters at that scale; for a user base in the hundreds or low thousands, every opted-in machine should report weekly.
The heavier cryptographic and statistical techniques are the wrong size for Arkos. Prio requires "two independent parties each process a separate portion of the data"; Mozilla ran both servers itself for a 1% Nightly pilot and said production "will require an independent third party to run one of the servers" [16]. Divvi Up packages the IETF DAP protocol with a leader and a helper aggregator, which can be self-hosted but still means a second operator [17]. Local differential privacy needs populations in the hundreds of thousands: Apple's deployments use dictionaries of 250,000 Safari domains at epsilon 4, with "no device identifiers", IPs "immediately discarded" and output histograms containing "Only elements above a threshold" [18], and Microsoft's repeated-collection mechanisms were built for "millions of devices" [19]. Russ Cox rejected Prio and Prochlo for Go as unnecessary for data this non-sensitive, and the same holds for feature counters [20].
The self-updater's manifest fetch is already a periodic, per-install network request, which is why Omaha formalised counting on top of it and why Firefox's census was literally "the number of conforming requests to the Firefox blocklist endpoint" made "not more than once per 24 hours" [4]. Three pitfalls follow. If the manifest is fetched straight from GitHub, there are no logs to count, so the check must be routed through, or mirrored to, the maintainer's service. If it is served through a CDN, counts must come from CDN logs or the request must be made uncacheable, which is what crates.io's migration was about [9]. And ephemeral machines inflate "new installs": Go's answer is that "The privacy feature of waiting at least a week before uploading anything at all" means "ephemeral machines such as build containers will never be counted" [1], Homebrew's is the CI flag [3], and Omaha's is testsource [5].
Publishing the aggregates needs a floor on cell size, and the right number for Arkos is far below Mozilla's. The disclosure-control literature says the choice "remains unresolved": ten is the most common threshold in national statistics and research outputs, five and twenty are also used, and three is the only value with an objective statistical foundation, which most organisations consider too thin [21]; health networks sit at eleven (PCORnet) and five (PEDSnet) [22]. Mozilla's data-publishing policy uses a minimum bucket of 5,000 for public dimension-level aggregates with smaller combinations folded into "Other", excludes web-activity and highly sensitive categories from the safe dimensions, and notes that "As sensitivity increases, minimum bucket size becomes increasingly important" [23]. A 5,000 floor would suppress everything Arkos has; a floor of ten with sub-threshold cells merged into "Other" is the defensible choice, and dimension discipline matters more than the number: with only version, macOS major, architecture and channel as dimensions, cross-tab cells are few and large, whereas locale, exact OS build or screen resolution are what create singleton cells.
Fifteen years of developer-tool incidents show one failure pattern and two survival patterns. The pattern that fails is default-on plus third-party analytics plus no in-product notice before the first send, usually with a corporate owner. Homebrew turned analytics on in April 2016 after a month of opt-in testing, announced it only on a mailing list and Twitter about a week ahead, and issue #142 argued that "most people never change defaults" and that enterprises forbid data leaving the company; the lead maintainer closed it the next day, locked it, and told one user to use another package manager [24]. On the MacPorts list in 2020, Saagar Jha wrote that disclosure "did eventually get implemented" but switching off Google Analytics or going opt-in "weren't", and a former core Homebrew maintainer replied that "there were a lot of Homebrew users who did in fact have a problem with it" [25]; Homebrew moved to a self-hosted EU InfluxDB in 4.0.0 (February 2023), removed Google Analytics by June 2023, destroyed the GA data in 4.1.0, and no longer uses an analytics UUID at all [26], [27]. Even so, LWN wrote in November 2025 that the notice "is easy to miss" and that users should be asked during installation [28].
The .NET SDK has been default-on since 2016 with DOTNET_CLI_TELEMETRY_OPTOUT; the installer sends one telemetry entry on success, and to avoid it the user must "close the installer, set the environment variable, and then run the installer again" [7], which commenters called a dark pattern [29]. The 2016 issue ("There is no indication at the time of install that the dotnet cli is automatically opted-into data sharing"; "We are asking for opt-in with explicit consent, not opt-out") closed with no reversal [30]. .NET Core 2.0 then added a hashed MAC address "to determine the aggregate number of machines", and in 2020 a security researcher argued the MAC hash lets a specific machine's data be located and that a default-on tool cannot meet GDPR consent [31], [32].
Audacity's PR #835 (May 2021) proposed libcurl plus Universal Google Analytics and Yandex Metrica, admitted both record the requester's IP, and drew roughly 3,316 thumbs-down against 246 thumbs-up before being locked and deleted [33]. The replacement (discussion #889, 419 thumbs-up) dropped all telemetry, kept error reporting via self-hosted Sentry on an EU server as opt-in per event with "send" and "don't send" buttons and a remember-my-choice checkbox unchecked by default, kept the update check on by default with a preference to disable it, and let source builds and Linux packages exclude both via CMake [34]. Even that was not the end: the July 2021 privacy notice's law-enforcement clause and under-13 wording triggered "spyware" headlines and the Tenacity fork, and LWN judged the data "not remotely as bad as it has been portrayed" but the handling damaging [35].
Go is the clearest case because the design was already excellent. Russ Cox's February 2023 design was opt-out; discussion #58409 drew 506 comments, the post itself about 159 upvotes against 507 downvotes, and a top comment with about 282 upvotes arguing that telemetry in developer tools should be off by default, citing CI farms and Bazel builds [36]. Two weeks later Cox published "Opting In to Transparent Telemetry", listing as his reasons that "make it opt-in" was the most common feedback, that some users would opt in but would turn it off if it were on by default, re-identification worries about obscure configurations, and that "If Go's telemetry were opt-out, that might be used to justify other opt-out systems that are not as careful about what they collect" [20]. The opt-in version shipped in Go 1.23 in August 2024 [37]. Fedora's 2023 proposal to deploy the Endless OS metrics stack with the consent toggle shown pre-enabled in initial setup, explicitly "to ensure the system is opt-out, not opt-in", was called a dark pattern and withdrawn [38]; its successor promised "an explicit yes/no prompt with no default value" and was itself dropped and marked CLOSED WONTFIX in April 2026 [39].
The survival patterns are both visible in the same table. Default-on survives when the storage is first-party, a notice precedes the first send, and a documented switch exists: VS Code's telemetry.telemetryLevel (all, error, crash, off) [40], Homebrew after 2016, .NET, Next.js, Astro, Nx and Zed all fit, though each still draws periodic "should be opt-in" issues, and VS Code's distributed binary is why VSCodium exists [41]. GitHub CLI joined this group in April 2026 and was immediately criticised for enabling it by default [42]. Opt-in survives with outreach: Go reports about 1,800 weekly participants after prompting 5% of VS Code Go users, and its one-year retrospective says "Opt-in telemetry needs continuous outreach to get a sample large enough for analysis", "Prompting boosts participation but can skew the sample", and "Even a few counters can surface bugs that users would rarely report" [37]. JetBrains disables sharing "in all release versions" and enables it in EAP builds [43]. The Rust toolchain has no upload telemetry at all; the 2023 internals thread proposes local-only compiler metrics, and participants drew the line at "user information should never leave their machine in an automated manner" [44].
| Tool | Usage telemetry default | Crash reports | Switches | Backend |
|---|---|---|---|---|
| Go (1.23+) | local (collected, never uploaded) |
stack counters, same consent | go telemetry on/off/local |
own open-source server, raw uploads published [45] |
| Homebrew | on, notice before first send | build-error events only | brew analytics off, HOMEBREW_NO_ANALYTICS=1 |
EU InfluxDB, 365 days, no UUID [3] |
| JetBrains IDEs | off in release, on in EAP | separate EAP checkbox | settings checkboxes | first-party [43] |
| VS Code | on (all) |
included in all and crash |
telemetry.telemetryLevel |
Microsoft [40] |
| Zed | on | on (minidumps on next launch) | telemetry.metrics, telemetry.diagnostics |
Sentry, Snowflake, Amplitude [46] |
| Audacity (post-2021) | none | opt-in per event, remember unchecked | Preferences, CMake | self-hosted Sentry, EU [34] |
| Bun | none | on (macOS, Windows), ~150-byte URL | DO_NOT_TRACK=1 |
bun.report [47] |
| GitHub CLI (2026) | on | not stated | GH_TELEMETRY=false, DO_NOT_TRACK |
GitHub [48] |
| Signal Desktop | none | user-initiated debug log | none needed | manual paste into issue [49] |
| rustc, cargo | none | none | none needed | none [44] |
Two anti-patterns deserve naming because they are structural rather than political. Zed's wire format is Event::Flexible(FlexibleEvent { event_type: String, event_properties: HashMap<String, Value> }), an open-ended property bag that makes a field-by-field disclosure impossible to keep honest [50], and Zed users found that disabling both metrics and diagnostics did not stop agent-panel thumbs up and down from sending data [51]. Scarf's founder said what was "really unpopular with people was the idea of analytics calls that send data off to Scarf or some third party", and the company pivoted to registry-side counting [52]. Third-party analytics endpoints, not counting itself, were the sharpest trigger in every incident.
The accepted consent patterns rank clearly. The most accepted is an explicit yes/no prompt with no default, which Fedora adopted after the pre-enabled toggle was condemned [39], Nx uses on first interactive run ("If you decline, telemetry is disabled for that workspace"; "Telemetry only runs in CI if analytics is explicitly set to true in nx.json") [53], and Audacity uses per crash [34]. Next is off-by-default with an in-product way to turn on and a one-time discoverable invitation, which is Go's local mode: "By default, Go telemetry data is stored only on your local computer", uploading needs go telemetry on, and go telemetry off disables even local collection [45]. The local default is the key political move, because it makes "install and inspect before you decide" the normal path rather than an expert one. Tolerated but perennially contested is on-by-default with a printed notice before the first send and a documented switch (Homebrew, .NET, Next.js, Astro).
The condemned patterns are equally specific: a toggle pre-set to on in a setup wizard (Fedora), announcement on a mailing list instead of in the product (Homebrew 2016), an opt-out that must be set before the installer runs (.NET), a per-machine opt-out that cannot be committed to a repository (Astro closed the request as not planned, Next.js users asked for a config-file key) [54], [55], an opt-out whose on-disk effect is undocumented (Astro docs issue #5432, Next.js #10713) [56], [57], and an opt-out that does not stop all traffic, which is VSCodium's origin story, the Zed feedback buttons, and n8n's feature flags breaking a product feature when diagnostics were off [41], [51]. Nx shows the operational cost of a missing env var: with Google Analytics firewalled, a synchronous flush on process exit hung forever and there was no environment-variable opt-out [58]. Warp's mandatory login drew enough sustained criticism to be lifted in November 2024, with coverage noting telemetry was still on by default [59].
The cross-tool convention is one line: if DO_NOT_TRACK is set to 1, "disable all tracking", a scope that covers "ad tracking, usage reporting, telemetry, crash reporting, and non-essential requests", respected "alongside your existing opt-out mechanisms" [60]. It is a community proposal, not a standard; the original consoledonottrack.com domain now serves unrelated content, so link to donottrack.sh. Documented adopters include GitHub CLI (DO_NOT_TRACK=true in its example) [48] and Bun, whose docs say "If DO_NOT_TRACK=1, then auto-uploading crash reports and telemetry are both disabled" [47]. Implementations differ on accepted values, so the safe rule is "any non-empty value other than 0 or false disables", stated on the telemetry page. KDE's policy adds the system-level counterpart: applications must honour system-wide telemetry settings where they exist, telemetry "is always opt-in. That means off by default", and the disclosure must be "available locally without network connectivity" [61].
Transparency artefacts are what separates trusted implementations from tolerated ones, and all are cheap. VS Code writes events to telemetry.log, shows them live in a Telemetry output channel, and code --telemetry dumps "a JSON report of all possible events" per build, each annotated with a GDPR classification and purpose [40]. Go stores counter files locally, shows them with gotelemetry view, keeps uploaded copies in an upload directory, and derives the upload allowlist from the public chart config, so "unlisted counters, such as private import paths, are never reported" and every new chart needs a public proposal issue [45], [1]. Homebrew's HOMEBREW_ANALYTICS_DEBUG=1 prints the request, GitHub CLI's GH_TELEMETRY=log prints the payload to stderr instead of sending, Next.js's NEXT_TELEMETRY_DEBUG=1 prints events and does not send, and Zed exposes "open telemetry log" from the Help menu [3], [48], [62], [46]. Go's trick of making the public chart config the upload allowlist collapses "what we collect" and "what we publish" into one reviewable file, which a one-person project can copy even though it cannot run a Mozilla-style data-review board.
Across Rust's standard library, Zed, Mozilla's crash annotations, Apple's .ips format and Sentry's minidump guidance, the irreducible core of a crash report is the same five things: an exact build identity (version, commit and the per-image debug UUID), a machine-resolvable stack of instruction addresses plus the loaded-image table rather than symbol names, the fault classification (panic kind, or signal plus exception code, or an error-kind enum), coarse environment (OS version, architecture, GPU vendor for a rendering app), and an incident id with timing. Heap memory, free-text panic payloads, file paths, hostnames and user identifiers are not needed for root-cause analysis and are exactly where personal data hides. Apple's own format shows the shape: each frame carries imageIndex and imageOffset, usedImages carries uuid, base, size and name, the exception block carries type, signal and codes, and "If you add [imageOffset] to the base value from the binary images list, you'll get the runtime memory address of the instruction that's executing" [63]. Apple also strips identity from its own reports: "macOS replaces user-identifable path components with placeholder values to protect privacy" (/Users/USER/*/...), reports from Xcode's organizer omit thread names, and "Application Specific Information is sometimes elided from a crash report to avoid logging privacy-sensitive information in the message" [64].
A naive Rust hook leaks through three channels. The panic payload is a String containing whatever was formatted: "A invocation of the panic!() macro in Rust 2021 or later will always result in a panic payload of type &'static str or String", and location() "will currently always return Some" with file() and line() [65], so expect("failed to open {path}"), an unwrap() on an io::Error whose display includes the OS message, or an assert_eq! on a struct holding user text all ship user data. The source location is an absolute build path like /Users/<name>/Projects/... unless remapped. The backtrace's Display includes at /abs/path/file.rs:line whenever debug info is found, and std warns that "Instruction pointers, symbol names, filenames, line numbers, etc, may all be incorrect when reported" [66]. A minidump adds a fourth channel: Crashpad captures "Selected OS-related process state, such as the command line and environment" and stack memory [67], and Sentry warns dumps "might contain sensitive information on the target system," such as environment variables, local pathnames, or input field contents including passwords, which is why it excludes the heap by default [68]. Chrome's own disclosure says its reports include "Memory related to the crash, which may include page contents, payment information, and passwords" [69].
The mitigations have shipping precedent, cheapest first. Compile-time --remap-path-prefix rewrites paths "in all output, including compiler diagnostics, debug information, macro expansions", with a macro scope that covers std::file!(), "where paths in embedded panic messages come from"; on Apple platforms the linker's OSO entries "are not remapped by the compiler and need to be manually remapped with -oso_prefix", build-script output may keep un-remapped paths, and the remap is "purely textual", so both the relative and absolute prefixes must be mapped [70]. Sending instruction pointers and module base addresses (the backtrace crate's Frame::ip() and module_base_address()) instead of Display strings removes the backtrace channel entirely [71]. For the payload, Zed's strip_user_string_from_panic removes the quoted content from std slicing panics such as "byte index" and "begin > end", replacing it with <redacted> to avoid uploading "arbitrary user text in crash reports", and Zed truncates abort messages to a 200-character first line [72], [73]. A PanicKind enum for the app's own code paths, Zed-style redaction for std and library panics, and otherwise a hash of the message plus a "message withheld" boolean (stable grouping without content) covers the rest; the hashing step is this report's suggestion rather than a documented vendor technique.
Allowlist beats denylist here. Sentry's server-side scrubbing is on by default and catches values that look like credit cards and keys named password, secret, token or bearer, but "Only a select number of fields are scrubbed", and frame function, filename, abs_path and module are not on the default list [74], [75]; its own advice is that the SDK "purposefully does not send PII to stay on the safe side" and that paths should be scrubbed in before_send because server-side rules only limit what is stored [76]. Mozilla's model is structural: each of roughly 200 crash annotations has a scope, and client (the default) means "never sent remotely"; about 67 have scope: ping and carry build, time and memory facts plus stack traces, while URL, Comments, AbortMessage and graphics adapter IDs are report-only and travel only with the consented full report [77]. For a hand-built schema this means the serializer should know only named, typed fields; extra, tags and free-form maps should not exist in the wire format.
Server-side symbolication is a one-maintainer-sized job. Build with debug info but ship without it: Cargo's split-debuginfo = "packed" produces a .dSYM on macOS, debug = "line-tables-only" generates "the minimal amount of debug info for backtraces with filename/line number info", and strip = "debuginfo" keeps the symbol table, which matters because rustc says programs that "want crash-reporting, should usually avoid -Cstrip=symbols" [78], [79]. Since Rust 1.77, profiles without debug info strip debuginfo by default because the precompiled standard library ships with it [80]. Apple's rule ties the two halves together: "The compiled binary and its companion dSYM file are tied together by a build UUID that's recorded by both", they "are only compatible with each other when they have identical build UUIDs", and "Keep the dSYM files for the specific builds you distribute" [81]. Resolution then uses atos -arch <arch> -o <dSYM>/Contents/Resources/DWARF/<binary> -l <load-address> with -i to reveal inlined frames [82], Sentry's symbolic library, which reads Mach-O and DWARF and demangles Rust [83], or Breakpad .sym files produced by dump_syms and consumed by minidump-stackwalk [84], [85]. Bun does exactly this at scale: its crash report is a roughly 150-byte URL encoding platform, subcommand, commit, a feature-flag integer, stack addresses and crash type, "containing zero personal information", symbolicated server-side so the binary need not ship 9 MB to 250 MB of symbols [86]. Self-hosted Sentry buys all of this plus grouping and a scrubbing UI, at the cost of a Docker deployment needing 4 cores, 16 GB RAM plus 16 GB swap with 32 GB recommended, Kafka and ClickHouse, and "no guarantees or dedicated support" [87]; a hand-built collector only needs Crashpad's contract, an HTTPS POST plus metadata with the handler limiting "one upload per hour" and the expectation that "the client application will manage the user's upload consent" [67].
The capture mechanism can be out-of-process or borrowed from Apple. Zed re-launches its own executable as a crash handler, has the panic hook log the message and span, send them to the handler, then call std::process::abort() so one minidump path covers panics and native crashes, writing <session_id>.dmp and .json into the logs directory [72]; the EmbarkStudios crash-handler and minidumper crates and minidump-writer provide the same pieces, and minidump-writer offers sanitize_stack() to replace non-pointer stack words with a sentinel [88], [89]. The lower-privacy alternative is to read the .ips file macOS already wrote: since macOS 12 the system stores crash reports as JSON with procName, bundleID, incident_id, the exception block, frames as image index plus offset, and the image table with UUIDs [63], user-readable through Console's Crash Reports view [90]. That gives a "last session crashed" detector with no stack memory, no environment and Apple-scrubbed paths, at the cost of no custom annotations unless the app writes a sidecar before aborting and joins on time. Apple's Developer Technical Support is blunt about the alternative: "Implementing your own crash reporter has significant privacy implications", "If you implement your own crash reporter, discuss the privacy impact with a lawyer", "Some information is so privacy-sensitive that it has no place in a crash report", and never call exit from a signal handler, so that Apple's own report is preserved [91].
Daemon and update failures need no stack at all. Mozilla classifies "outcome of automated processes like updates" with "OS, crashes and errors" as Category 1 technical data, eligible for default-on collection in release [92], and its annotations are enumerated facts (MozCrashReason, SecondsSinceLastCrash, UptimeTS, AsyncShutdownTimeout, AvailablePhysicalMemory) rather than prose [77]. For Arkos's exec-in-place updater the record is an enum step (download, verify, stage, swap, restart, rollback), an enum outcome, an io::ErrorKind or HTTP status or exit code, from-version and to-version, elapsed time and a disk-free bucket, with the same build identity; a rollback is the same record with outcome = rolled_back and a reason enum. No URLs with tokens, no paths, no stderr blobs.
The consent pattern that all four shipping implementations converge on is: write locally first, never upload from inside the crashing process, decide on next launch, throttle, delete after upload or on decline, and keep a local list. Firefox moves dumps into a pending directory, deletes them "If the user chooses not to submit a dump in the crash reporter UI", and lists past and submitted crashes in about:crashes [93]; its dialog offers "Tell {vendor} about this crash so they can fix it", a separate unchecked "Include the address of the page I was on", a comment box labelled "(comments are publicly visible)", and a "Details…" button that opens a "Report Contents" view [94]. Chromium stores reports locally and uploads only after opt-in, with chrome://crashes listing each report and its upload status [95]. Signal Desktop counts pending dumps on startup, shows "Signal restarted after a crash. You can submit a crash a report" with Send and Don't Send, and erases the dumps on Don't Send or close [96]. Zed uploads on the next launch automatically when diagnostics are enabled, with no per-incident prompt, which is the one counter-example rather than an accepted norm [46]. Mozilla's own tiering gives the rule: counts of crashes and errors are Category 1 and may be default-on with an off switch, while "memory contents, dumps" are Category 4, which "requires prior Trust approval and (i) advance user notice (ii) consent and (iii) an opt-out" [92]. If the wire format is a fixed, typed allowlist, "show exactly what will be sent" is literally the serialized struct, so no prose approximation is needed.
This section is research into published guidance and case law, not legal advice; the points marked as lawyer questions are exactly that.
Under the GDPR, identifiability is judged by "all the means reasonably likely to be used", including singling out, and pseudonymised data that could be attributed to a person with additional information remains personal data, while the principles "do not apply to anonymous information" [97]. The Article 29 Working Party's anonymisation opinion is the reference for hashing: pseudonymisation "is a useful security measure but not a method of anonymisation", hash functions "are subject to brute force attacks" where the input range is known, a salted hash "may still be feasible with reasonable means" to reverse, and under pseudonymisation "It is still possible to single out individuals' records" [98]. The EDPB's 2025 pseudonymisation guidelines add the detail that matters for daily-rotating tokens: "If the controller selects the date when the pseudonymising transformation was applied as parameter, the entropy of this parameter will be very low", they prefer MACs and password-hashing functions such as argon2 over bare hashes, and they confirm that under Article 11 a controller that "does not have (or no longer has) access to additional information allowing attribution" need not identify data subjects [99]. The CJEU's September 2025 EDPS v SRB judgment then made identifiability relative: pseudonymised data is not personal data "in all cases and for every person", and where a recipient lacks the means reasonably likely to re-identify, the data may not be personal for that recipient, though the controller's transparency duty applies regardless [100]. The EDPB's draft anonymisation guidelines of July 2026 (consultation open until 30 October 2026) replace the 2014 tests with "No Record Isolation, No Linkage and No Inference" and adopt the same entity-specific standard [101], and the UK ICO frames it as the "whose hands?" question: pseudonymised data "is personal data in the hands of someone who holds the additional information" and may be anonymous in a recipient's hands without it [102].
The consequence for Arkos is that a per-day hash of a machine id is a pseudonym, defensible but arguable, whereas Go's construction sidesteps the argument: with no per-machine token, counters aggregated client-side, and the uploader's IP "not recorded with the reports", uniqueness is not even available to the server, so neither singling out nor linkage is possible from the stored data [1]. KDE's policy takes the same line ("we will not use any unique device, installation or user id") [61]. If per-day deduplication were ever needed, an HMAC with a client-generated secret the server never sees would answer both the "no secret" and "low-entropy date" criticisms; the better answer is not to need it.
The IP address is unavoidable at the TCP layer, and the law has a settled place for it. In Breyer (October 2016) the CJEU held a dynamic IP is personal data for a website operator with legal means to identify the person, and that storing it "in order to make it possible to trace and defend against attacks" can be a legitimate interest, subject to balancing [103]; GDPR Recital 49 now covers network and information security. The practice of privacy-conscious services is uniform: Go does not record it, Plausible never stores it, Let's Encrypt's temporary logs are "normally deleted in less than seven days" with a subset kept only while an abuse investigation runs [104], and Matomo masks the last bytes before storage while noting masked data "is often still personal data" [105]. The disclosure these converge on is one sentence: the server sees the IP to accept the request; it is not written to disk, or it is truncated and discarded after N days.
ePrivacy is the sharper constraint, because it does not depend on personal data. The EDPB's 2023 guidelines on Article 5(3) (version 2.0, October 2024) hold that the article covers "information" in a broader sense than personal data, that where software on the device produces information locally and it is "sent back over the network to a server, such an operation ... would constitute a 'gaining of access to information already stored'", that "The fact that this information is being produced locally does not preclude the application of Article 5(3)", and that client-side code instructing the device to send a hashed persistent identifier is likewise gaining access [106]. The exemption is for storage or access "strictly necessary in order for the provider of an information society service explicitly requested by the subscriber or user to provide the service", and the EDPB stresses that applicability "does not systematically mean that consent needs to be collected", only that the exemption must be assessed case by case [106]. Read against a locally running IDE, the exemption fits the update check (the user installed a self-updating tool) and fits neither usage counters nor crash uploads; the lowest-risk reading is that both need consent, which an opt-in prompt supplies, and Fedora Legal reached the same conclusion that any PII collection would force the whole system to be opt-in [38]. Firefox names its legal basis for crash reports as consent "when you choose to send us crash reports" [107], and Apple's own macOS analytics is sent "only with your consent" with a separate "Share with app developers" switch [108].
If any personal data is processed, Article 13 requires a notice with the controller's identity and contact, purposes and legal basis, the legitimate interests pursued, recipients, transfers, retention, the data-subject rights, the right to withdraw consent, and the right to complain to a supervisory authority [109]; Article 11 lets a controller whose purposes do not require identification say so and decline to acquire identifying information for access or erasure requests [110]. Those add four or five sentences to a telemetry page, not a separate document. Whether an individual US maintainer with EU users is subject to the GDPR at all under the Article 3(2) targeting test was outside the research; the practical consensus among the projects reviewed is to write the page as if it applies.
The US baseline is accuracy. The FTC's position is that "a material misstatement or omission about a product's privacy features, which is likely to mislead a reasonable consumer, can be deceptive" under Section 5, which applies to small developers as much as large [111], so the words "anonymous" and "never stored" on the page must be literally true of the code. CalOPPA applies to operators of a "commercial" website or online service collecting personal information from California residents and requires the policy to list categories collected and shared [112]; whether a no-revenue hobby tool is "commercial" is a lawyer question with low stakes (the remedy is a 30-day cure notice). The CCPA applies only to for-profit businesses with over $26,625,000 in gross revenue (the threshold adjusted on 1 January 2025), that buy, sell or share the personal information of 100,000 or more Californians, or that derive half their revenue from selling it [113], [114]; a free one-person project meets none. COPPA applies to a general-audience service only with "actual knowledge" of collecting from under-13s [115], so an age clause of the kind Audacity added creates friction without legal benefit. Apple adds nothing for a Homebrew-distributed Developer ID app: the App Privacy "nutrition labels" are an App Store Connect submission requirement [116], and notarization is a malware scan that reviews neither behaviour nor privacy [117]. If Arkos were ever submitted to the Mac App Store, counters and crash uploads would need declaring under Diagnostics, and ongoing collection after an initial permission cannot use the optional-disclosure exemption.
The pages people trust share a structure, and each element can be traced to a project that publishes it. The table below lists the nine items, with the project whose page demonstrates each and the gap that commenters noticed when it was missing.
| Item | Demonstrated by | What goes wrong without it |
|---|---|---|
| Field-by-field table with an example value and the reason each field exists | Homebrew's payload list [3]; GitHub CLI's sample payload [48]; Go's counter names [45] | "Anonymous" becomes a claim rather than a checkable fact |
| Explicit "not collected" list (paths, file names, contents, IP, user id) | Homebrew ("no user identifier or an IP-address field") [3]; Next.js exclusions [62] | Users assume the worst from the incidents they remember |
| Default state and exact enable, disable and status commands plus the env var | Go (go telemetry on/off/local) [45]; Homebrew (brew analytics state) [3] |
Astro's env var went undocumented for a period [118] |
| A way to print the exact payload before it is sent | HOMEBREW_ANALYTICS_DEBUG=1 [3]; GH_TELEMETRY=log [48]; gotelemetry view [45] |
Users cannot verify the page against the binary |
| Transport, storage system, hosting location and retention in days | Homebrew (InfluxDB, EU, 365 days) [3] | GitHub CLI's page omits retention and IP handling, which The Register picked on [48], [42] |
| Who can read raw versus aggregate data | Homebrew ("Most Homebrew maintainers have access only to these public aggregates") [3]; KDE (all developer-account holders) [61] | Trust rests on the maintainer's word alone |
| Pointer to the source files that are authoritative if the page drifts | Homebrew names analytics.rb and analytics.sh [3]; Zed points at CrashInfo and the event enum [46] |
The page and the code diverge silently |
| A change process | Go's public proposal issue per counter [45]; KDE's rule that changes are "highlighted in the corresponding release announcement" [61] | Each new field is a new surprise |
| Published results, or the schema of what is published | Go's public raw uploads and charts [1]; Homebrew's 30, 90 and 365-day reports [11] | Users carry the cost and see no benefit |
Two further sentences come from the legal section: the maintainer's name and contact as controller, the legal basis per purpose (legitimate interest for momentary IP handling, consent for counters and crash reports), retention, the Article 11 statement that individuals cannot be identified so access and erasure requests cannot be fulfilled for aggregated data, and the right to complain to a supervisory authority [109], [110]. KDE's requirement that the disclosure be available offline means the same text ships inside the app, reachable from the settings surface, not only on the website [61]. Mozilla's policy adds the habit the page should promise: "We make our decisions about data collection public and accessible", with an annual re-review of existing collections [92]. Homebrew's self-limiting clause is worth copying verbatim in spirit: analytics "must not replace technical evidence or maintainer judgement, and low usage alone doesn't prove removal is safe" [3].
The design has three tiers that differ in consent, and the tier boundaries follow Mozilla's categories and the ePrivacy analysis above. Tier 0 is the update check, which the user accepted by installing a self-updating tool; Tier 1 is the weekly usage report, which is off by default and sends counters only; Tier 2 is the crash and error report, which asks per incident and shows its bytes. One table defines the wire format for all three; nothing outside it exists in the serializer.
| Tier | Field | Type and example | Why it exists |
|---|---|---|---|
| 0, 1, 2 | app_version |
string, 0.2.0 |
version adoption; selects the update manifest |
| 0, 1, 2 | binary |
enum, app or daemon |
separates GUI and updater populations |
| 0, 1, 2 | os_major |
string, 15 (macOS major only) |
OS distribution without singleton cells |
| 0, 1, 2 | arch |
enum, arm64 or x86_64 |
build targeting |
| 0, 1, 2 | channel |
enum, tap, direct, source |
distribution split; source never uploads |
| 1 | install_week |
integer days since epoch, rounded down to a multiple of 7 | new versus returning installs (Omaha X - X % 7) |
| 1 | days_since_last_report |
integer, 7; -1 on first report |
unique weekly actives (Omaha rd) |
| 1 | days_since_last_active |
integer, 2; -1 if never |
daily, weekly and monthly actives and retention (Omaha ad) |
| 1 | report_freshness |
random 128-bit value, rotated with each stored report date | detects cloned or restored machines, never reused across reports |
| 1 | ci |
boolean | separates automation; true suppresses prompts and uploads |
| 1 | counters |
map from allowlisted name to u64, for example session.start, session.len:<5m, agent.launch:claude, pane.browser.open, update.applied, exit.clean, exit.crash |
feature use, session-length histogram, crash-free rate, agent mix |
| 2 | kind |
enum, panic, signal, update_step |
failure class |
| 2 | commit and image_uuid |
short SHA; main image LC_UUID | selects the archived dSYM |
| 2 | panic_kind or message_hash plus message_withheld |
enum from the app's own code paths; otherwise SHA-256 of the redacted first line and a boolean | grouping without content |
| 2 | file and line |
remapped path such as /src/crates/daemon/update.rs, integer |
locates the panic |
| 2 | frames |
list of (image index, offset) pairs for the faulting thread | symbolicated server-side |
| 2 | images |
list of (uuid, base, size, name) for shipped images only | symbolication; nothing the server does not already know |
| 2 | exception |
enum type, signal, code (fault address) | native crash classification |
| 2 | rosetta, memory_pressure, gpu_vendor, uptime_bucket |
boolean; enum; enum; enum of ranges | environment for rendering and resource crashes |
| 2 | update |
step enum, outcome enum, io::ErrorKind or HTTP status, from and to versions, elapsed ms, disk-free bucket |
self-update and rollback diagnosis, no stack needed |
| 2 | incident_id |
random UUID per incident, never stored beyond the report | dedup and user-facing receipt |
Nothing in any tier carries a hostname, username, home directory, file name, project name, environment variable, argument vector, prompt, agent transcript, stack memory, heap, raw panic string, IP address or persistent identifier. report_freshness is the one random value, and it is unlinkable by construction because the client discards it when it stores the next report date, exactly as Omaha specifies [5].
Defaults are: Tier 0 on, with a documented updates.check = false switch that also disables the daemon's network access; Tier 1 in local mode, meaning counters accumulate on disk in a human-readable file and nothing uploads until the user runs arkos telemetry on; Tier 2 prompting per incident with "Always send crash reports" unchecked. The Tier 1 invitation is shown once, after the install is at least seven days old and only in an interactive session, as a dismissable banner rather than a modal, linking to the field table; the seven-day wait is Go's device for keeping ephemeral machines out of the count, and the interactive-only rule is Nx's [1], [53]. DO_NOT_TRACK set to any non-empty value other than 0 or false disables Tiers 1 and 2 and the invitation; ARKOS_TELEMETRY=off and the config key do the same; a CI environment variable or a non-TTY session suppresses prompts and uploads regardless of mode; arkos telemetry off also stops local collection, as Go's does [45]. The source channel never uploads, matching Audacity's CMake exclusion and Go's go.env provision for distributions [34].
Consent for Tier 2 follows Firefox and Signal. Both binaries write the typed JSON report to disk before any decision; the daemon never prompts, it queues. On the next interactive launch the app shows one dialog: a sentence on what happened, a "Show report" button that renders the exact bytes, Send and Don't send, and the unchecked "Always send" box. Don't send deletes the file; Send uploads at most one report per hour, deletes on success and keeps for retry on failure; a "Reports" view lists past incidents with their server receipt, mirroring about:crashes [93], [67]. Free text, if ever added, sits behind a second unchecked checkbox, as Firefox's page-address option does [94]. Apple's .ips file is the fallback detector when the out-of-process handler itself fails, and the app must never call exit from a signal handler so Apple's report survives [91].
Transport is an HTTPS POST of JSON to a first-party endpoint on the maintainer's domain, with no cookies, no query-string identifiers, a user agent of arkos/<version>, sent from a detached background task with a short timeout and silent failure so telemetry can never block the user, which is Homebrew's rule [3]. Tier 1 reports go weekly on a per-install random weekday to spread load, as Go does [1]. Build configuration for Tier 2 is debug = "line-tables-only", split-debuginfo = "packed", strip = "debuginfo", --remap-path-prefix for both the Cargo home and the source root plus -oso_prefix for the linker, and a release step that archives the .dSYM keyed by UUID and runs dump_syms into a symbol directory [78], [70], [81], [84].
Server hygiene is where the page's claims become true. The collector writes no access log, or if the host writes one it cannot disable, truncates the IP and keeps it under seven days with the retention stated, following Let's Encrypt [104]. It computes no hash of anything; it validates the schema, rejects unknown fields, drops reports whose channel is source or whose ci is true from the official counts, and stores counters, not events. Raw Tier 1 reports are harmless by construction, so publishing them in full as Go does is possible, but the recommended default is to keep raw reports private, keep them for 365 days as Homebrew does, and publish daily aggregates per (version, os_major, arch, channel) with any cell under ten merged into "Other" [21], [23]. Tier 2 reports are symbolicated offline by a script that runs symbolic or minidump-stackwalk against the archive, and the symbolicated result is what the maintainer reads; the raw address list is deleted after symbolication. The collector is open source in the same repository, so the claim that IPs are discarded "can be confirmed by inspecting the reporting server source code", which is the one thing Cox said users must trust [1].
Disclosure is a telemetry.md page containing the nine items above and the Article 13 sentences, shipped inside the app as well as on the site; a telemetry/counters.toml allowlist in the repository that is both the upload filter and the published schema, so adding a counter is a reviewable pull request; arkos telemetry view to print the local counters and the last report, and ARKOS_TELEMETRY=log to print any payload to stderr instead of sending it; and a line in every release note that touches collection, as KDE requires [61].
The trade-offs are real and should be stated on the page. Opt-in means the sample is small and self-selected; with a user base in the hundreds, Tier 1 will report tens of machines, enough for feature counters and crash-free rate but not for fine cohorts, and Go's experience is that the sample needs continuous outreach while prompting skews it toward the prompted surface [37]. Counters instead of events mean no funnels or sequences; the question "what did the user do before the crash" is answered only by the enum fields in the crash report. No identifier means retention is measured from the two days-since fields, not per-machine longitudinally, and cloned-machine detection relies on the freshness value rather than on an ID. Counting Tier 0 update checks per day measures machines that are switched on, not humans at the keyboard; it is Firefox's ADI, with ADI's known inflation from always-on machines and deflation from laptops asleep. Address-only crash reports require the maintainer to archive every release's dSYM and run a symbolication script, and lose the deep memory-corruption cases that only stack memory would resolve; that loss is the price Mozilla and Chrome also pay for their default-on tier, and they gate the rest behind employee-only access [119]. The cheapest fully defensible alternative, Bun's and Signal's model of no automatic upload and a compact report the user pastes into an issue, costs the usage counts entirely, and Go's year of data says the stack counters, not the usage counts, are what find the bugs users never report [37], [86]. The design above keeps both at the cost of one dialog and one weekly POST the user chose.
The research reframes the question. The risk in developer-tool telemetry was never the counting; it was identifiers, third-party endpoints, free-form payloads and silent defaults, and each of those is a design choice with a shipping alternative. Go proved that a toolchain can run telemetry with no IDs, no IP retention and public raw data; Omaha proved that unique actives and retention need only two integers the client already knows; Bun proved a crash report fits in 150 bytes when symbols stay on the server; and Apple, Firefox and Signal proved that users accept a crash dialog that shows its bytes. Put together, Arkos can have install counts, actives, retention, feature and agent mix, and symbolicated crash stacks while its telemetry page states, truthfully, that it has never received a file name, a path, a prompt or an identity.
The decision that remains is political rather than technical: whether to accept a small opt-in sample in exchange for never having to defend a default. Every project in the record that chose the default paid for it, some with forks, and the one that reversed in time (Go) now reports no backlash and a steady stream of real bugs from a few thousand volunteers. For a one-maintainer project whose users are the exact population that wrote the Go and Rust threads, the local default plus a visible invitation is not the cautious choice; it is the one that gets the data.
- [1]R. Cox, "The Design of Transparent Telemetry," research!rsc. Accessed: Oct. 15, 2026. [Online]. Available: https://research.swtch.com/telemetry-design
- [2]Go Authors, "Go telemetry privacy policy," telemetry.go.dev. Accessed: Oct. 15, 2026. [Online]. Available: https://telemetry.go.dev/privacy
- [3]Homebrew, "Analytics," Homebrew Documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://docs.brew.sh/Analytics
- [4]Mozilla, "Censuses (ADI)," Mozilla Data Documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://docs.telemetry.mozilla.org/concepts/censuses
- [5]Chromium Authors, "Omaha updater protocol 3.1," Chromium source documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://chromium.googlesource.com/chromium/src/+show/main/docs/updater/protocol_3_1.md
- [6]Astro, "Astro telemetry policy," astro.build. Accessed: Oct. 15, 2026. [Online]. Available: https://astro.build/telemetry/
- [7]Microsoft, ".NET SDK telemetry," Microsoft Learn. Accessed: Oct. 15, 2026. [Online]. Available: https://learn.microsoft.com/en-us/dotnet/core/tools/telemetry
- [8]GitHub, "REST API endpoints for release assets," GitHub Docs. Accessed: Oct. 15, 2026. [Online]. Available: https://docs.github.com/en/rest/releases/assets
- [9]Rust Blog Team, "Changes to crates.io downloads," Rust Blog, Mar. 11, 2024. Accessed: Oct. 15, 2026. [Online]. Available: https://blog.rust-lang.org/2024/03/11/crates-io-download-changes/
- [10]crates.io Authors, "Count only cargo user agents (pull request #12210)," GitHub, 2025. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/rust-lang/crates.io/pull/12210
- [11]Homebrew, "Homebrew public analytics reports," formulae.brew.sh. Accessed: Oct. 15, 2026. [Online]. Available: https://formulae.brew.sh/analytics/
- [12]Plausible Analytics, "Data policy," plausible.io. Accessed: Oct. 15, 2026. [Online]. Available: https://plausible.io/data-policy
- [13]GoatCounter, "Sessions," GoatCounter Help. Accessed: Oct. 15, 2026. [Online]. Available: https://www.goatcounter.com/help/sessions
- [14]C. Sarkosh, "lib-visits," GitHub repository. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/csarkosh/lib-visits
- [15]Mozilla Add-ons Team, "Blocklist pings and unique IPs," Mozilla Add-ons Blog. Accessed: Oct. 15, 2026. [Online]. Available: https://blog.mozilla.org/addons/?p=783
- [16]Mozilla Security Team, "Next steps in privacy-preserving telemetry with Prio," Mozilla Security Blog, Jun. 6, 2019. Accessed: Oct. 15, 2026. [Online]. Available: https://blog.mozilla.org/security/2019/06/06/next-steps-in-privacy-preserving-telemetry-with-prio/
- [17]Divvi Up, "Divvi Up documentation," divviup.org. Accessed: Oct. 15, 2026. [Online]. Available: https://docs.divviup.org/
- [18]Apple Machine Learning Research, "Learning with privacy at scale," Apple. Accessed: Oct. 15, 2026. [Online]. Available: https://machinelearning.apple.com/research/learning-with-privacy-at-scale
- [19]B. Ding, J. Kulkarni, and S. Yekhanin, "Collecting telemetry data privately," arXiv:1712.01524, 2017. Accessed: Oct. 15, 2026. [Online]. Available: https://arxiv.org/pdf/1712.01524
- [20]R. Cox, "Opting in to transparent telemetry," research!rsc. Accessed: Oct. 15, 2026. [Online]. Available: https://research.swtch.com/telemetry-opt-in
- [21]F. Ritchie, "10 is the safest number that there's ever been," University of the West of England repository. Accessed: Oct. 15, 2026. [Online]. Available: https://uwe-repository.worktribe.com/index.php/OutputFile/7459485
- [22]countmaskr Authors, "countmaskr README: PCORnet and PEDSnet cell-size rules," CRAN. Accessed: Oct. 15, 2026. [Online]. Available: https://cloud.r-project.org/web/packages/countmaskr/readme/README.html
- [23]Mozilla, "Data publishing," Mozilla Wiki. Accessed: Oct. 15, 2026. [Online]. Available: https://wiki.mozilla.org/Data_Publishing
- [24]Homebrew Contributors, "Analytics should be opt-in (issue #142)," GitHub, Homebrew/brew. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/Homebrew/brew/issues/142
- [25]A. Janke, "Re: Call for designers for our ports website," macports-dev mailing list, Jun. 13, 2020. Accessed: Oct. 15, 2026. [Online]. Available: https://lists.macports.org/pipermail/macports-dev/2020-June/041967.html
- [26]Homebrew, "Homebrew 4.0.0," Homebrew release notes, Feb. 16, 2023. Accessed: Oct. 15, 2026. [Online]. Available: https://brew.sh/2023/02/16/homebrew-4.0.0
- [27]Homebrew, "Homebrew 4.1.0," Homebrew release notes, Jul. 20, 2023. Accessed: Oct. 15, 2026. [Online]. Available: https://brew.sh/2023/07/20/homebrew-4.1.0
- [28]LWN.net, "Homebrew's analytics notice," LWN.net, Nov. 2025. Accessed: Oct. 15, 2026. [Online]. Available: https://lwn.net/Articles/1046236
- [29]Jenk, comment on ".NET SDK telemetry," Hacker News, Nov. 29, 2022. Accessed: Oct. 15, 2026. [Online]. Available: https://news.ycombinator.com/item?id=33784919
- [30]dotnet/sdk Contributors, "Telemetry should be opt-in (issue #6145)," GitHub. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/dotnet/sdk/issues/6145
- [31]Microsoft, "What we've learned from .NET Core SDK telemetry," .NET Blog, 2017. Accessed: Oct. 15, 2026. [Online]. Available: https://devblogs.microsoft.com/dotnet/what-weve-learned-from-net-core-sdk-telemetry/
- [32]D. Leadbeater, "Microsoft .NET SDK is violating the GDPR, object now," dgl.cx, Aug. 2020. Accessed: Oct. 15, 2026. [Online]. Available: https://dgl.cx/2020/08/dotnet-sdk-gdpr
- [33]Audacity Contributors, "Basic telemetry (pull request #835)," GitHub. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/audacity/audacity/pull/835
- [34]Audacity Contributors, "Telemetry decision (discussion #889)," GitHub. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/audacity/audacity/discussions/889
- [35]LWN.net, "Audacity privacy policy controversy," LWN.net, Jul. 2021. Accessed: Oct. 15, 2026. [Online]. Available: https://lwn.net/Articles/862073/
- [36]Go Authors, "Transparent telemetry (discussion #58409)," GitHub, golang/go. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/golang/go/discussions/58409
- [37]Go Authors, "Telemetry in Go 1.23 and beyond," The Go Blog, Sep. 2024. Accessed: Oct. 15, 2026. [Online]. Available: https://go.dev/blog/gotelemetry
- [38]Fedora Project, "Changes/Telemetry," Fedora Project Wiki. Accessed: Oct. 15, 2026. [Online]. Available: https://fedoraproject.org/wiki/Changes/Telemetry
- [39]Fedora Project, "Changes/Metrics," Fedora Project Wiki. Accessed: Oct. 15, 2026. [Online]. Available: https://fedoraproject.org/wiki/Changes/Metrics
- [40]Microsoft, "Telemetry," Visual Studio Code Documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://code.visualstudio.com/docs/configure/telemetry
- [41]Visual Studio Magazine, "VSCodium," Visual Studio Magazine, Apr. 2019. Accessed: Oct. 15, 2026. [Online]. Available: https://visualstudiomagazine.com/Articles/2019/04/17/vscodium.aspx
- [42]The Register, "GitHub opts all CLI users into telemetry," The Register, Apr. 2026. Accessed: Oct. 15, 2026. [Online]. Available: https://www.theregister.com/2026/04/22/github_opts_all_cli_users/
- [43]JetBrains, "Data sharing settings," IntelliJ IDEA Help. Accessed: Oct. 15, 2026. [Online]. Available: https://www.jetbrains.com/help/idea/settings-usage-statistics.html
- [44]Rust Internals Contributors, "No telemetry in the Rust compiler: metrics without betraying user privacy," Rust Internals forum. Accessed: Oct. 15, 2026. [Online]. Available: https://internals.rust-lang.org/t/no-telemetry-in-the-rust-compiler-metrics-without-betraying-user-privacy/19275
- [45]Go Authors, "Go telemetry," go.dev. Accessed: Oct. 15, 2026. [Online]. Available: https://go.dev/doc/telemetry
- [46]Zed Industries, "Telemetry," Zed Documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://zed.dev/docs/telemetry
- [47]Bun, "Environment variables," Bun Documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://bun.sh/docs/runtime/env
- [48]GitHub, "GitHub CLI telemetry," GitHub Docs. Accessed: Oct. 15, 2026. [Online]. Available: https://docs.github.com/en/github-cli/github-cli/github-cli-telemetry
- [49]Signal, "Debug logs," Signal Support. Accessed: Oct. 15, 2026. [Online]. Available: https://support.signal.org/hc/articles/360007318591
- [50]Zed Industries, "telemetry_events.rs," GitHub repository. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/zed-industries/zed/blob/main/crates/telemetry_events/src/telemetry_events.rs
- [51]Zed Contributors, "Feedback data sent with telemetry off (discussion #31488)," GitHub. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/zed-industries/zed/discussions/31488
- [52]TechCrunch, "Scarf helps open-source developers track how their projects are being used," TechCrunch, Mar. 3, 2021. Accessed: Oct. 15, 2026. [Online]. Available: https://techcrunch.com/2021/03/03/scarf-helps-open-source-developers-track-how-their-projects-are-being-used/
- [53]Nx, "Telemetry reference," Nx Documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://nx.dev/docs/reference/telemetry
- [54]Astro Contributors, "Per-project telemetry opt-out (issue #11820)," GitHub. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/withastro/astro/issues/11820
- [55]Next.js Contributors, "Config-file telemetry opt-out (discussion #31400)," GitHub. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/vercel/next.js/discussions/31400
- [56]Astro Contributors, "Document what telemetry disable changes (docs issue #5432)," GitHub. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/withastro/docs/issues/5432
- [57]Next.js Contributors, "Telemetry needs to be disabled without voodoo (issue #10713)," GitHub. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/vercel/next.js/issues/10713
- [58]Nx Contributors, "Telemetry flush hang with no env-var opt-out (issue #35979)," GitHub. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/nrwl/nx/issues/35979
- [59]Warp, "Lifting the login requirement," Warp Blog. Accessed: Oct. 15, 2026. [Online]. Available: https://warp.dev/blog/lifting-login-requirement
- [60]Console, "Console Do Not Track proposal," donottrack.sh. Accessed: Oct. 15, 2026. [Online]. Available: https://donottrack.sh/
- [61]KDE Community, "Telemetry policy," KDE Community Wiki. Accessed: Oct. 15, 2026. [Online]. Available: https://community.kde.org/Policies/Telemetry_Policy
- [62]Vercel, "Next.js telemetry," nextjs.org. Accessed: Oct. 15, 2026. [Online]. Available: https://nextjs.org/telemetry
- [63]Apple, "Interpreting the JSON format of a crash report," Apple Developer Documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://developer.apple.com/documentation/xcode/interpreting-the-json-format-of-a-crash-report
- [64]Apple, "Examining the fields in a crash report," Apple Developer Documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://developer.apple.com/documentation/xcode/examining-the-fields-in-a-crash-report
- [65]Rust Project, "Struct PanicHookInfo," Rust standard library documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://doc.rust-lang.org/std/panic/struct.PanicHookInfo.html
- [66]Rust Project, "Module backtrace," Rust standard library documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://doc.rust-lang.org/std/backtrace/index.html
- [67]Crashpad Authors, "Crashpad overview design," Chromium source documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://chromium.googlesource.com/crashpad/crashpad/+/main/doc/overview_design.md
- [68]Sentry, "Minidumps (native)," Sentry Documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://docs.sentry.io/platforms/native/guides/minidumps/
- [69]Google, "Usage statistics and crash reports," Google Chrome Help. Accessed: Oct. 15, 2026. [Online]. Available: https://support.google.com/chrome/answer/96817
- [70]Rust Project, "Remapping source paths," The rustc Book. Accessed: Oct. 15, 2026. [Online]. Available: https://doc.rust-lang.org/rustc/remap-source-paths.html
- [71]backtrace-rs Authors, "Struct Frame," docs.rs, backtrace crate. Accessed: Oct. 15, 2026. [Online]. Available: https://docs.rs/backtrace/latest/backtrace/struct.Frame.html
- [72]Zed Industries, "crates/crashes/src/crashes.rs," GitHub repository. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/zed-industries/zed/blob/main/crates/crashes/src/crashes.rs
- [73]Zed Industries, "crates/zed/src/reliability.rs," GitHub repository. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/zed-industries/zed/blob/main/crates/zed/src/reliability.rs
- [74]Sentry, "Server-side data scrubbing," Sentry Documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://docs.sentry.io/product/data-management-settings/scrubbing/server-side-scrubbing/
- [75]Sentry, "Event PII fields," Sentry Documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://docs.sentry.io/security-legal-pii/scrubbing/server-side-scrubbing/event-pii-fields.md
- [76]Sentry, "Sensitive data," Sentry Rust SDK documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://docs.sentry.io/platforms/rust/data-management/sensitive-data/
- [77]Mozilla, "CrashAnnotations.yaml," Mozilla source repository. Accessed: Oct. 15, 2026. [Online]. Available: https://hg-edge.mozilla.org/mozilla-central/raw-file/tip/toolkit/crashreporter/CrashAnnotations.yaml
- [78]Cargo Authors, "Profiles," The Cargo Book. Accessed: Oct. 15, 2026. [Online]. Available: https://doc.rust-lang.org/cargo/reference/profiles.html
- [79]Rust Project, "Codegen options," The rustc Book. Accessed: Oct. 15, 2026. [Online]. Available: https://doc.rust-lang.org/rustc/codegen-options/index.html
- [80]Rust Blog Team, "Announcing Rust 1.77.0," Rust Blog, Mar. 21, 2024. Accessed: Oct. 15, 2026. [Online]. Available: https://blog.rust-lang.org/2024/03/21/Rust-1.77.0/
- [81]Apple, "Building your app to include debugging information," Apple Developer Documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://developer.apple.com/documentation/xcode/building-your-app-to-include-debugging-information
- [82]Apple, "Adding identifiable symbol names to a crash report," Apple Developer Documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://developer.apple.com/documentation/xcode/adding-identifiable-symbol-names-to-a-crash-report
- [83]Sentry, "symbolic," GitHub repository. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/getsentry/symbolic
- [84]Mozilla, "dump_syms," GitHub repository. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/mozilla/dump_syms
- [85]rust-minidump Authors, "rust-minidump," GitHub repository. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/rust-minidump/rust-minidump
- [86]Bun, "bun.report is Bun's new crash reporter," Bun Blog, Apr. 2024. Accessed: Oct. 15, 2026. [Online]. Available: https://bun.com/blog/bun-report-is-buns-new-crash-reporter
- [87]Sentry, "Self-hosted Sentry," Sentry developer documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://develop.sentry.dev/self-hosted/
- [88]Embark Studios, "crash-handling," GitHub repository. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/EmbarkStudios/crash-handling
- [89]rust-minidump Authors, "minidump-writer," GitHub repository. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/rust-minidump/minidump-writer
- [90]Apple, "Acquiring crash reports and diagnostic logs," Apple Developer Documentation. Accessed: Oct. 15, 2026. [Online]. Available: https://developer.apple.com/documentation/xcode/acquiring-crash-reports-and-diagnostic-logs
- [91]Apple Developer Technical Support, "Implementing your own crash reporter," Apple Developer Forums. Accessed: Oct. 15, 2026. [Online]. Available: https://developer.apple.com/forums/thread/113742
- [92]Mozilla, "Data collection," Mozilla Wiki. Accessed: Oct. 15, 2026. [Online]. Available: https://wiki.mozilla.org/Data_Collection
- [93]Mozilla, "Crash reporter," Firefox Source Docs. Accessed: Oct. 15, 2026. [Online]. Available: https://firefox-source-docs.mozilla.org/toolkit/crashreporter/crashreporter/index.html
- [94]Mozilla, "crashreporter.ftl strings," Mozilla source repository. Accessed: Oct. 15, 2026. [Online]. Available: https://hg-edge.mozilla.org/mozilla-central/raw-file/tip/toolkit/locales/en-US/crashreporter/crashreporter.ftl
- [95]Chromium Authors, "Crash reports," chromium.org. Accessed: Oct. 15, 2026. [Online]. Available: https://www.chromium.org/developers/crash-reports/
- [96]Signal Authors, "Crash report dialog (commit 91f1b62)," GitHub, Signal-Desktop. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/signalapp/Signal-Desktop/commit/91f1b62bc742ea25e8d7c58b5b6c5adff617381a
- [97]European Union, "GDPR Recital 26," GDPR Info. Accessed: Oct. 15, 2026. [Online]. Available: https://gdpr-info.eu/recitals/no-26/
- [98]Article 29 Data Protection Working Party, "Opinion 05/2014 on anonymisation techniques (WP216)," European Commission, 2014. Accessed: Oct. 15, 2026. [Online]. Available: https://ec.europa.eu/justice/article-29/documentation/opinion-recommendation/files/2014/wp216_en.pdf
- [99]European Data Protection Board, "Guidelines 01/2025 on pseudonymisation," EDPB, 2025. Accessed: Oct. 15, 2026. [Online]. Available: https://www.edpb.europa.eu/system/files/2025-01/edpb_guidelines_202501_pseudonymisation_en.pdf
- [100]A&O Shearman, "CJEU clarifies concept of personal data for a transfer of pseudonymised data to third parties (EDPS v SRB)," A&O Shearman. Accessed: Oct. 15, 2026. [Online]. Available: https://www.aoshearman.com/en/insights/ao-shearman-on-data/cjeu-clarifies-concept-of-personal-data-for-a-transfer-of-pseudonymised-data-to-third-parties
- [101]Slaughter and May, "Anonymous to whom? The EDPB's new draft guidelines on anonymisation," The Lens, Jul. 2026. Accessed: Oct. 15, 2026. [Online]. Available: https://thelens.slaughterandmay.com/post/102nc00/anonymous-to-whom-the-edpbs-new-draft-guidelines-on-anonymisation
- [102]UK Information Commissioner's Office, "Pseudonymisation," ICO guidance. Accessed: Oct. 15, 2026. [Online]. Available: https://ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/data-sharing/anonymisation/pseudonymisation/
- [103]T. Helbing, "CJEU Breyer case summary," thomashelbing.com. Accessed: Oct. 15, 2026. [Online]. Available: https://www.thomashelbing.com/en/wissen/dsgvo-hub/rechtsprechung/1.4.37-eugh-breyer
- [104]Let's Encrypt, "Privacy policy," letsencrypt.org. Accessed: Oct. 15, 2026. [Online]. Available: https://letsencrypt.org/privacy/
- [105]Matomo, "How does IP address anonymisation work in Matomo," Matomo FAQ. Accessed: Oct. 15, 2026. [Online]. Available: https://matomo.org/faq/general/how-does-ip-address-anonymisation-work-in-matomo/
- [106]European Data Protection Board, "Guidelines 2/2023 on technical scope of Art. 5(3) of ePrivacy Directive, v2.0," EDPB, 2024. Accessed: Oct. 15, 2026. [Online]. Available: https://edpb.europa.eu/system/files/2024-10/edpb_guidelines_202302_technical_scope_art_53_eprivacydirective_v2_en_0.pdf
- [107]Mozilla, "Firefox privacy notice," mozilla.org. Accessed: Oct. 15, 2026. [Online]. Available: https://www.mozilla.org/en-US/privacy/firefox/
- [108]Apple, "Share analytics information from your Mac," Apple Support. Accessed: Oct. 15, 2026. [Online]. Available: https://support.apple.com/guide/mac-help/share-analytics-information-mac-mh27990/mac
- [109]European Union, "GDPR Article 13," GDPR Info. Accessed: Oct. 15, 2026. [Online]. Available: https://gdpr-info.eu/art-13-gdpr/
- [110]European Union, "GDPR Article 11," GDPR Info. Accessed: Oct. 15, 2026. [Online]. Available: https://gdpr-info.eu/art-11-gdpr/
- [111]U.S. Federal Trade Commission, "Statement on Section 5 and privacy representations," FTC, May 2016. Accessed: Oct. 15, 2026. [Online]. Available: https://www.ftc.gov/system/files/documents/public_statements/948633/160511fccprivacyrules.pdf
- [112]Consumer Federation of California, "California Online Privacy Protection Act (CalOPPA)," Consumer Federation of California. Accessed: Oct. 15, 2026. [Online]. Available: https://consumercal.org/about-cfc/cfc-education-foundation/california-online-privacy-protection-act-caloppa-3/
- [113]California Attorney General, "California Consumer Privacy Act (CCPA)," State of California Department of Justice. Accessed: Oct. 15, 2026. [Online]. Available: https://oag.ca.gov/privacy/ccpa
- [114]California Privacy Protection Agency, "CPI-adjusted monetary thresholds," CPPA. Accessed: Oct. 15, 2026. [Online]. Available: https://www.cppa.ca.gov/regulations/cpi_adjustment.html
- [115]U.S. Federal Trade Commission, "Complying with COPPA: frequently asked questions," FTC Business Center. Accessed: Oct. 15, 2026. [Online]. Available: https://ftc.gov/tips-advice/business-center/guidance/complying-coppa-frequently-asked-questions
- [116]Apple, "App privacy details on the App Store," Apple Developer. Accessed: Oct. 15, 2026. [Online]. Available: https://developer.apple.com/app-store/app-privacy-details/
- [117]Apple, "Gatekeeper and runtime protection," Apple Platform Security. Accessed: Oct. 15, 2026. [Online]. Available: https://support.apple.com/guide/security/gatekeeper-and-runtime-protection-sec5599b66df/web
- [118]Astro Contributors, "Documenting the telemetry env var (roadmap discussion #405)," GitHub. Accessed: Oct. 15, 2026. [Online]. Available: https://github.com/withastro/roadmap/discussions/405
- [119]Mozilla, "Protected data access," Mozilla Crash Stats. Accessed: Oct. 15, 2026. [Online]. Available: https://crash-stats.mozilla.org/documentation/protected_data_access/