# Usage statistics (what is sent, and the two ids that let us count you once)

> The toolchain sends usage counts — command names, versions, OS, error codes and durations — with two random ids: one per installation, so a person is counted once, and one per project, so a started app can be followed to its first deploy. Never your source, your data, your paths, your name or your address. One command turns it off, another resets the ids, and the first run tells you it is on.

<!-- id: project-statistics · area: project · stability: preview · html: https://osysharp.com/reference/project/statistics/ -->

## Summary   {#summary}

The toolchain sends usage counts so that the verbs people reach for, the versions they run and the errors they hit
can be counted — and so that the people trying it can be counted once, and an app somebody starts can be followed
to the moment it first deploys. Two random ids make that possible, and this page says exactly what they are and
what they are not. It never sends your source, your data, your paths, your name, your machine name or your
address. `osy telemetry off` stops it, durably; `osy telemetry reset` replaces the ids with new ones; and the
first run of the toolchain says that it is on and how to turn it off.

## Signature   {#signature}

```console
osy telemetry off      # stop sending, durably
osy telemetry on       # start again
osy telemetry reset    # new installation id — the old one's events can no longer be joined to yours
osy telemetry status   # the setting, where it is stored, your version and the newest one the receiver has named
```

## Description   {#description}

**What is sent.** Each run of an `osy` or `osyrin` verb records one event, locally, immediately — sending it is a
separate step (see "Sent in batches" below):

| field | example | why |
|---|---|---|
| the verb | `launch`, `check`, `init` | which parts of the toolchain are used at all |
| the toolchain version | `0.9.3` | whether a report is about the current release |
| the operating system and architecture | `darwin/arm64` | which builds have to exist |
| the exit code, and an error code when there is one | `CommandParseException` | which refusals people hit most, so their wording can be fixed |
| the duration | `2140 ms` | which verbs are slow |
| the installation id | `9f1c…` (32 random hex characters) | so a person is counted once — see "Two random ids" below |
| the project id | `4a7e…`, when the verb ran inside a project | so a started app can be followed to its first deploy |

**What is never sent.** Your source. Your data. File or directory paths. App names, entity names, function names,
or any other identifier from your project. Your user name, machine name, IP address or e-mail. The text of a
diagnostic (only its type — the class of what went wrong, e.g. `CommandParseException`, never the message that
came with it, which can quote back whatever the command was given). Anything typed into a prompt.

**One deliberate, narrow exception: what you typed into `osy docs`.** A search term for the reference is not a
prompt and not project data — it is a question about the toolchain itself — but it is still text you typed, so it
travels as its own separate, explicitly-named field rather than being folded silently into the general shape:
what you asked (`"Session.SignOut"`), and whether the reference answered it (`Page`, `Ambiguous`, `Miss`, …). This
is the field that turns "the reference sometimes fails" into "the reference fails on THESE terms" — the same gap
`osy docs --misses` cannot see on its own, because that command only ever looks at one machine's own local log.

**Sent in batches, not on every run.** The local event above is free — it is a few bytes on disk. Actually sending
anything anywhere is rate-limited to happen at most a few times an hour, at the very most, whichever run happens
to be the one due — a session running many commands back to back does not generate a burst of network traffic for
each one. A send that fails (the destination is unreachable) is not retried sooner; it waits for the next window,
the same as a successful one would.

**Two random ids, disclosed.** Each event carries an **installation id** — a random value written to
`~/.osy/config.json` the first time the toolchain runs — and, from a project, a **project id** — a random value
`osy init` writes into that project's `osyrin.json`. Neither is derived from anything: not your hardware, your
user name, your e-mail or the project's name. They exist for two questions the counts alone cannot answer: *how
many people* are trying the toolchain (one installation id, counted once, however many runs), and *does an app
that gets started get finished* — a project id joins that project's `init`, its first green `osy check`, its first
`osy launch` and its first deploy into one story, and the time between them. From the two together: projects per
person, and how far the typical person gets. **What they are never used for:** joining to a person. There is no
name, address or account anywhere in the data to join them to, and nothing in the toolchain sends one. Because a
random id that persists is pseudonymous rather than anonymous, this page says so rather than calling the data
anonymous: it identifies an installation, not a human, and `osy telemetry reset` breaks even that link whenever
you like. Raw events are kept for ninety days and then only the weekly counts remain.

**Where people use it — counted by region, never stored by event.** The receiving server learns where a batch
came from and adds it to a count: this region, this week, this verb. The country and its first subdivision are
kept — a US state, a Swedish county, a German Land — because a map of where the toolchain is used is one of the
things the statistics exist for, and "United States" says much less than fifty states do. The connection's address
is used for one lookup at the edge and discarded before anything is stored; where the ingress sits behind a CDN
the location arrives as a header and the address is never seen at all. What is stored is the count, not the event:
there is no row that says one run happened in one place at one time, so there is nothing that could be traced to a
person however few people a region has. The "never your IP address" above stays literally true — it is not a
field, it is not stored, and it is not sent by the toolchain.

**Where it goes, and for how long.** Batches are sent at most once an hour — and right away after `osy init`,
`osy launch` and a deploy, the three moments the funnel is made of, so a first try that goes no further still
counts — to `osysharp.com`, or to whatever
`OSY_TELEMETRY_URL` names — a self-hosted platform receives its own toolchain's statistics through the same route
(`/api/telemetry/v1`), so an organisation that runs its own can keep them entirely in-house. The receiver keeps
three things: the raw events for ninety days and then deletes them; one row per installation id and one per
project id (first seen, last seen, the funnel's four timestamps, the region); and the weekly counts, which are all
that remains after the ninety days. A batch that cannot be sent — no network, the receiver down — waits on disk
for the next hour and costs the command that tried nothing beyond a three-second cap; the waiting log is bounded,
and past a few thousand lines the oldest are dropped.

**The update check is a separate request, to the release channel, and it is anonymous to us.** Once a day the
toolchain asks GitHub's releases page for `osysharp/cli` — the same place Homebrew, winget and the install script
download from — which version is the newest. GitHub sees a request with the toolchain's user agent and nothing
else; the statistics receiver is not involved and never learns who asked. When the answer is newer than the one you
are running, the toolchain prints one line, once a day, before the command's own output:

```console
a newer osy is available: 0.9.1 (you have 0.9.0) — osy upgrade
  what's new: osy whats-new   ·   https://github.com/osysharp/cli/releases/tag/v0.9.1
```

`osy upgrade` is the one command that installs it on every platform — through the same download door, checked
against the release's checksums, swapped in place ([Upgrading the toolchain](https://osysharp.com/reference/local/upgrading-the-toolchain/)). `osy whats-new` prints the
release's notes in the terminal, from the same public page, and `osy whats-new 0.9.1` reads a particular version's. It stays on when statistics are off,
because it is not a statistic; `"updateCheck": false` in `~/.osy/config.json`
turns it off for a machine that wants no request it did not type, and `osy telemetry status` shows the running and
the latest published version either way.

**Disclosed at first run.** The first time the toolchain runs it prints one line saying that usage statistics
are on, that they carry a random installation id, and that `osy telemetry off` stops them. It does not ask a question, because a question in
the middle of `osy init` is exactly what nobody reads; it tells you, once, where you will see it.

**Where the setting lives.** `~/.osy/config.json`, in the toolchain's own configuration directory, not in your
project — so it is a choice you make once per machine, and it is not something a checkout can quietly turn back
on. `osy telemetry off` stops LOCAL RECORDING too, not only sending — turning it off means the toolchain stops
writing these events at all, not just stops mailing them.

**Feedback is separate, and it is yours to send.** `osy feedback` opens a prefilled issue on the toolchain's
public tracker ([github.com/osysharp/cli](https://github.com/osysharp/cli)), with your version and the last
diagnostic the toolchain hit — an internal fault, or the last "✗" refusal a command printed — already filled in — a real GitHub issue form you still review and edit before you
submit it. That is a message you write and send; it is not a statistic, and turning statistics off does not
affect it. Pass `--print-url` to see the link without opening a browser.

**`osy feedback --full` adds a diagnostic bundle, and the issue never holds it.** The bundle is a zip built on
your machine: the last diagnostic, the tail of the local run log, the project's `osyrin.json` and `osyrin.lock`
and a list of its files, and the log tails of the local platform serving it. Never your `.secrets`, never an
operator key, never the database. Your `.osy` sources go in only with `--with-source`. The contents are listed
before anything leaves the machine, and then the zip is uploaded **privately** to osysharp.com — into storage
only the maintainers can read — and the issue carries a short bundle id in its place. Anything attached to an
issue on a public repository is public, which is why the file is never attached. `--no-upload` keeps the zip on
disk instead, for you to attach or mail yourself.

```console
$ osy feedback --full "validate refuses a valid app"
diagnostic bundle — 14,212 bytes, 9 entries:
  manifest.txt
  toolchain.json  (131 bytes)
  last-diagnostic.json  (188 bytes)
  …
bundle uploaded privately as fb-3f9a2c17d0 — only the maintainers can read it; the issue carries the id.
Opening a prefilled issue on osysharp/cli — review it before you submit.
```

## Examples   {#examples}

```console
$ osy telemetry status
anonymous statistics: on
  sends: verb · version · os/arch · exit code · error code · duration (+ the search term, for `osy docs` only)
  never: source · data · paths · names · addresses · anything else typed
  ids:   a random installation id · a random project id — see above
  off:   osy telemetry off

$ osy telemetry off
anonymous statistics: off (stored in ~/.osy/config.json)

$ osy feedback "the docs page for X is wrong about Y" --print-url
https://github.com/osysharp/cli/issues/new?title=…&body=…
```

## See also   {#see-also}

- [Hosting an Osy# app](https://osysharp.com/reference/project/hosting/) — where an app runs, and the three places it can
- [The local loop (running your app on your own machine)](https://osysharp.com/reference/local/index/) — the local toolchain, verb by verb
