-
Notifications
You must be signed in to change notification settings - Fork 0
Telemetry
- Accshift counts a few anonymous things. It is a handful of counters, not a profile.
- Settings > Privacy, two switches, both off means nothing is ever sent again. Nothing is gated on it and the app is identical either way.
- Nothing at all is sent before you finish the first-launch screen. After it, the anonymous counters are on. That screen asks about the enhanced tier, not about the counters, so turning those off is a separate deliberate action. Said plainly: the anonymous tier is opt-out, the enhanced tier is opt-in.
- The
accshiftcommand-line binary reads the same two switches. - Data goes to a Cloudflare Worker that stores nothing, which forwards to PostHog in the EU. Resend is used only for operational alert emails.
The canonical version of this page lives in the repository, at
docs/analytics.md,
so that git log shows every change ever made to what is collected. Where this
page and the repository disagree, the repository is right.
Accshift manages game accounts, so this list matters more than the one after it. None of the following ever leaves your machine, in any mode:
- Account names, usernames, display names, nicknames
- Platform account identifiers of any kind (SteamID, Riot PUUID, Epic account id)
- Passwords, tokens, cookies, session files, or anything derived from them
- Persona names, folder names, custom labels, avatars
- File paths, directory names, screenshots, window contents
- Your IP address, which is never stored anywhere
- Log files. Logs stay on your machine and are only ever shared by hand, when you choose to attach one to a bug report
An event can say "an account was added on Steam". It cannot say which account, because the app never puts that in the payload.
Events are sent, and they are deliberately made hard to link together.
A random UUID is generated on your machine. The server never stores it: it stores a keyed hash of it, used only to avoid counting the same installation twice in the daily active count. Every other event is attributed to a different hash that changes every night, derived from your IP address and User-Agent, so two switches you make on Monday and Tuesday cannot be tied to the same installation.
No user profile is created on the analytics side. This mode exists so the project can answer "how many people use this, on what OS, in what country" and nothing more.
A second random UUID, the install id, is generated and attached to every event. It stays the same over time, which is exactly the point: it makes it possible to see whether people come back after a week, which features get used together, and how many accounts a real library holds.
This is the mode where a profile does exist on the analytics side. It is a separate opt-in for that reason, and it is the only mode where export and deletion are possible, because they need an identifier to act on.
When both switches are on, events are sent once, under the enhanced identifier. They are not duplicated. Turning the enhanced switch off falls back to the anonymous identifiers.
The command-line binary reads the same two switches. It reports one event per command, never sends the daily ping (a command you run five times is one person, not five), and with both switches off it starts no reporting at all.
Nineteen events, and that is the complete list:
| Event | When | Fields beyond the common ones |
|---|---|---|
ping |
Once a day while the app runs |
dropped_events, only if any were |
first_run |
The first launch of an installation | none |
app_launched |
Startup finished | duration_ms |
platform_switch |
You switched an account |
platform, duration_ms, success, error_code on failure |
persona_switch |
You activated a persona | number of platforms, number that succeeded |
account_add_started |
You opened an add-account flow | platform |
account_add_cancelled |
You closed one without adding an account | platform |
account_added |
You finished adding an account | platform |
operation_failed |
A named operation failed |
operation, error_code, platform
|
update_available |
An update was found | target_version |
update_downloaded |
It finished downloading | target_version |
update_applied |
It was installed | target_version |
update_failed |
Any of the three above failed |
target_version, error_code
|
cli_command |
A CLI command finished |
command, success, error_code
|
streamer_mode_activated |
Streamer mode auto-enabled | none |
deep_link_used |
An accshift:// link was opened |
none |
session_ended |
You closed the window | duration_ms |
accounts_snapshot |
Each app start, enhanced only |
platform, how many accounts on it |
settings_snapshot |
Each app start, enhanced only | the settings listed below |
One more event is not in that table because it is not tied to an installation
at all: consent_choice, recorded once when you answer the first-launch
screen. It carries the answer and the app version, and lands on a single shared
counter so the possible answers have a denominator.
Every event also carries seven common fields: the app version, a fixed OS
identifier (windows, macos, linux), the architecture, the OS version,
your locale (for example fr-FR), whether it came from the app or the CLI, and
the time it happened, to the second. The server adds the country code, derived
from your IP address without storing the address.
platform is always a fixed identifier like steam or riot, never anything
you typed. So are error_code, operation and command: each is matched
against a fixed list in the code, and anything unrecognised is recorded as
other. That is what stops an error message, which routinely contains a file
path with your username in it, from travelling inside one of those fields. The
server validates every property again before forwarding it, so a property the
code does not write cannot reach the analytics provider at all.
accounts_snapshot counts how many accounts exist per platform and nothing
else about your library. settings_snapshot reports your interface language,
which platforms are enabled, and whether personas, the PIN lock, the CLI and
deep links are on. No theme name, because a custom theme is one you named
yourself. Both are enhanced-only: the app drops them before upload in anonymous
mode.
Every release up to and including 1.0.2 skipped Steam in accounts_snapshot,
because the list it was built from only covered platforms whose accounts live
in the config file. Snapshots recorded by those releases say nothing about
Steam, in either direction.
first_run means "first launch that knew how to report one", so on an
installation that predates the release introducing it, it fires on the first
launch after the update rather than on the day it was installed.
This table is checked against the code on every release.
A real batch, exactly as it leaves the machine in anonymous mode:
{
"mode": "A",
"anonymous_id": "b3f1c2d4-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
"events": [
{
"name": "ping",
"app_version": "1.0.2",
"os": "windows",
"arch": "x86_64",
"os_version": "Windows 11 Pro 26200",
"surface": "gui",
"client_ts": "2026-08-04T12:34:56Z",
"locale": "fr-FR"
},
{
"name": "platform_switch",
"app_version": "1.0.2",
"os": "windows",
"arch": "x86_64",
"os_version": "Windows 11 Pro 26200",
"surface": "gui",
"client_ts": "2026-08-04T12:36:02Z",
"locale": "fr-FR",
"platform": "steam",
"duration_ms": 842,
"success": true,
"count": 1
}
]
}Batches are sent at most once every five minutes, the first about twenty
seconds after launch, and are held in memory only. Analytics is never written
to your disk, so an app that never reaches the network simply forgets its
events. A batch that fails to send is kept in memory and retried, the delay
doubling up to an hour; at most 200 events are held that way and the oldest are
discarded past that. ping reports how many were lost, which is the only
reason that count exists.
- Your machine sends the batch to a Cloudflare Worker, open source and readable
in
server/. - The Worker derives the country code from your IP address, computes the anonymous-mode hashes, and discards the address. It stores nothing itself: it has no database of any kind.
- It forwards the events to PostHog, in their EU region, hosted in Germany. Every forwarded event explicitly overrides the IP field and disables location lookup, so PostHog stores no address and infers no location beyond the country code computed in step 2.
Rate-limit alert emails contain a masked /24 IPv4 or /48 IPv6 prefix and
never an event payload.
On first launch the GUI shows a one-time screen. It asks whether you want the enhanced tier; declining leaves the anonymous counters on, and the note under the buttons says so and points at Settings > Privacy for the full opt-out.
The choice increments one aggregate counter carrying no installation identifier, so the project can tell whether the screen is understood. Even a refusal is recorded against a single shared id, never one tied to your machine.
Replay onboarding in Settings > Privacy shows the tour and that screen again.
Enhanced mode only. Settings > Privacy, Export my data copies everything held against your install id to the clipboard as JSON: the person properties and up to 10000 events.
Anonymous-mode events cannot be exported. They carry no install id and their identifier rotates nightly, which is the entire point of that mode.
Enhanced mode only, same reason.
Turning the enhanced switch off asks the server to delete everything tied to your install id. Before making that request the app moves the old id into a machine-local deletion queue, so a failed request stays pending across restarts, with Retry deletion and Export my data still available in Settings. The id leaves the queue only once the server confirms.
Your profile and its properties are removed immediately. The events themselves are queued for a batch job that the analytics provider runs during off-peak hours, weekly on their cloud. "Deleted" therefore means "irreversibly scheduled", not "already gone", so allow up to a week for that part. The job only sweeps events captured before the request; the app closes its queue before calling, so the residual window is a batch already on the wire, and a second deletion request clears any residue.
Re-enabling enhanced mode creates a new id and does not cancel older pending requests.
The enhanced tier runs on your explicit consent, GDPR Article 6(1)(a), and withdrawing it is one click that never degrades the app.
The anonymous tier is on after onboarding rather than opt-in, so the project relies on its legitimate interests in knowing whether the software is used and what to fix first, GDPR Article 6(1)(f). Your right to object is the switch in Settings > Privacy, which takes effect immediately and permanently. The same basis covers the identifier-free onboarding counter and the abuse-prevention controls on the endpoint.
Depending on your jurisdiction you may also have rights of information, access, rectification, erasure, restriction, portability and complaint to a data-protection authority. Export and deletion are in Settings > Privacy for the enhanced tier. Use GitHub Issues, without personal data, for anything the app cannot handle.
The full legal version is on the Privacy Policy page.
Events older than 12 months are deleted.
Once a month a handful of totals are copied to a machine the maintainer owns,
so the project can see a multi-year trend without keeping raw events. Those
totals contain no identifier of any kind: every value is a count, or a country,
version or platform label, summed over a whole month. The script that does it
is
scripts/monthly-stats-snapshot.sh,
and the five queries it runs are the whole of what is kept long term.
Nothing here has to be taken on trust. The code that decides what is sent is
small and self-contained: the event list and every field are in
crates/accshift-core/src/telemetry/events.rs, the payload in client.rs, the
queue in queue.rs, the consent gate in mod.rs, the CLI's own reporting in
crates/accshift-cli/src/telemetry.rs, and the server in server/src/index.ts.
All of them are linked from
docs/analytics.md.
Settings > Privacy:
- Toggle the enhanced switch off. This triggers the deletion request above; if Settings reports it pending, use Retry deletion when the service is reachable.
- Toggle the anonymous switch off.
No further event is sent after both are off. The one aggregate onboarding counter was recorded when the original choice was made and cannot be tied back to an installation.