CLI Pulse — Last Updated: November 12, 2026
Remote control between an iPhone and a Mac is built into the direct-download build but is off in this release: the Mac shows no Remote Control section in Settings until the feature is switched on, and we can switch it on later through the update information the app downloads. Once it is offered and you turn it on, the iPhone and the Mac connect to each other directly, over your local network or your own private network (such as Tailscale), encrypted with TLS 1.2 using a key the two devices agree on when you pair them; what they exchange does not pass through our servers.
| Data | Stored where | Sent to our server? | Purpose |
|---|---|---|---|
| Provider API keys you enter (OpenAI, Anthropic, Google, OpenRouter, etc.) | macOS Keychain on this Mac. The Android app has no place to enter one | ❌ Never | Sent only to that provider, to ask for your usage and quota |
| Provider session cookies you paste (manual cookie headers) | macOS Keychain on this Mac | ❌ Never | Sent only to that provider, as a Cookie header |
| Provider cookies read from your browsers, only for a provider whose cookie source is "Automatic" (Cursor's is until you change it; any other provider's only if you choose it), and not while Strict privacy mode is on | Read from that browser's cookie store (macOS may ask you to let CLI Pulse use the browser's "Safe Storage" Keychain item) | ❌ Never | Sent only to that provider |
Tokens that AI CLIs keep on your Mac: ~/.codex/auth.json, ~/.claude/.credentials.json, ~/.gemini/oauth_creds.json, ~/.local/share/kilo/auth.json, and Claude Code's Keychain item |
Read where each CLI keeps them. The app copies the file-based ones into a Keychain item that only CLI Pulse's own programs can read | ❌ Never | Sent only to that provider, for live quota (and, since v1.57, Codex's limit reset credits) |
Contents of your session logs (~/.codex/sessions/, ~/.codex/archived_sessions/, ~/.claude/projects/, ~/.config/claude/projects/) |
Read on your Mac: in the App Store build through folder access you grant, in the direct-download build directly | ❌ Never | Token counts and costs are worked out on your Mac |
| Daily usage (per day, provider and model: input, cached and output token counts and a cost estimate; which of your Macs it came from, once that Mac is paired) | Supabase, linked to your CLI Pulse account | ✅ Yes, while signed in | So iPhone, Apple Watch and the Android app show the same history as your Mac |
| Provider quota state (remaining, limit, plan, reset times; for a provider that sells prepaid credit, such as OpenRouter, DeepSeek or Venice, the credit balance and, where the provider reports it, how much credit was bought; the label you gave the account, if any; which Mac reported it) | Supabase, linked to your CLI Pulse account | ✅ Yes, while signed in | So mobile clients display current quotas without running the scanner themselves |
| AI CLI sessions running on your Mac (the program's name; the name of its project folder, never the full path; a keyed hash of that folder's path; when it started and was last active; an activity estimate based on how long it has run and its CPU use) | Supabase, linked to your CLI Pulse account | ✅ Yes, while signed in and this Mac is paired | So iPhone, Apple Watch and the Android app can show what is running on your Mac. The App Store build's sandbox usually hides other programs from it, and then it has none to send. The Companion CLI sends more; see its section |
| Alerts: your Mac or a program using a lot of CPU (with that program's name, provider and folder name; CLI Pulse 1.56 and earlier also raised one for a session running a long time), budget alerts our server works out from your daily usage, and whether and when you acknowledged each one (which marks it read), whether you resolved it, and until when you snoozed it | Supabase, linked to your CLI Pulse account | ✅ Yes, while signed in | So your other devices can show them |
| Your alert webhook, if you add one: the address, and which alerts go to it | Supabase, linked to your CLI Pulse account | ✅ Yes | Our server posts each matching alert (its title, message, type, severity and provider) to that address |
| Settings kept with your account (such as alert thresholds and budget, how many days ended sessions and alerts are kept, and whether Yield Score and remote control are on) | Supabase, linked to your CLI Pulse account | ✅ Yes, while signed in | So every device uses the same settings |
| This Mac's helper readings: CPU and memory load, how many AI CLI sessions are running, whether each installed AI CLI is signed in with a subscription or an API key (where the built-in agent or the Companion CLI can tell), and whether each provider's check worked, or why not (for example not signed in, or keychain access declined) | Supabase, linked to your CLI Pulse account | ✅ Yes, while signed in and this Mac is paired | Load and session count show on your other devices. The rest are diagnostics for us, such as why a provider shows nothing |
| Device name (your Mac's name as set in System Settings, which often contains your own name), macOS version, app version and helper version | Supabase, linked to your CLI Pulse account | ✅ Yes, when you pair this Mac, and the app version while it is paired | Shows which of your Macs are reporting. The versions are diagnostics for us |
| Your Android phone's maker and model (such as "Google Pixel 8", not a name you gave it) and its Android version, with fixed labels and the time the record was added. The app's background sync sends it the first time it runs after you sign in (at most every 15 minutes, while the phone is online and its battery is not low) | Supabase, linked to your CLI Pulse account | ✅ Yes, once per sign-in | Lists the phone among your devices. Signing out does not remove it, and each later sign-in on the phone adds another; the app cannot remove them, and they are deleted with your account |
| Machine readings (Companion CLI only): battery charge, health, cycle count and temperature, thermal state, temperature and fan sensors, load, uptime, memory pressure, swap and disk space, Low Power Mode, and the state of machine controls | Supabase, linked to your CLI Pulse account | ✅ Yes, while the Companion CLI is allowed to upload | The Machine view on iPhone and Apple Watch, and the Devices screen in the Android app |
| Requests you send from your iPhone to a Mac (fan boost, fan back to automatic, Low Power Mode, Keep Awake) | Supabase, linked to your CLI Pulse account | ✅ Yes, when you send one | Carried to that Mac, which acts on them only if you allowed it there |
| Your iPhone's notification token, only if you allow notifications while signed in | Supabase, linked to your CLI Pulse account | ✅ Yes | Lets our server ask your iPhone, through Apple, to refresh its widgets or to look at a pending request. These pushes carry none of your data |
| Your Android phone's notification token, which Google's Firebase Cloud Messaging gives the app. It is sent when Firebase hands the app a token while you are signed in and the phone is already listed in your account. Firebase usually hands over the first token when the app first starts, before you have signed in, so usually only a later replacement, if Firebase issues one while you are signed in, is sent | Supabase, with the phone's record in your account | ✅ Yes, if handed over while signed in | None yet: our server sends no notifications to Android phones and does not read this token, and the app does not ask for permission to show notifications. A token that was sent stays with that record after you sign out |
| Your CLI Pulse login email | Supabase Auth | ✅ Yes | Required to authenticate you. Asking for a sign-in code for an address creates an account for it, even if the code is never entered. We may also use it to email you occasionally about the product itself — a short survey, or notice of a change that affects you. Never marketing, never sold, never shared, and every such email carries a one-click opt out. See Product email below. The surveys are part of the analytics we do ourselves (see Analytics we do ourselves). |
| Your password, if you sign in with one | Supabase Auth | ✅ Yes, when you sign in with it | Signs you in. Supabase keeps it only as a hash |
| Your name and profile details from Apple, Google or GitHub, if you sign in with one of them (on iPhone, including one you link in Settings › Linked Accounts; on Android, Google or GitHub, including one you link in the Android app's Settings › Linked Accounts) | Supabase Auth | ✅ Yes, if that provider shares them | Supabase keeps what the sign-in provider hands it. When you sign in with Apple on iPhone, or with Google on Android, the app also passes on the name Apple or Google gives it |
| Apple / Google / GitHub sign-in tokens (during sign-in) | Not persisted — exchanged once for a Supabase session | ✅ Yes (during sign-in only) | Identity verification with the original OAuth provider |
| Supabase session access / refresh token | Keychain on each Apple device you sign in on; on Android, the app's encrypted preferences (cli_pulse_secure_prefs). Your iPhone passes it to your paired Apple Watch over Apple's own watch connection |
❌ Not uploaded as data: sent only to authenticate your requests and to renew your session | Keeps you signed in |
| What else the Android app keeps on the phone: your account's ID, name and email; the ID of the phone's record in your account; whether Try Demo is on; while a GitHub sign-in or account link you started in the browser is unfinished, a one-time code for finishing it; as last fetched from your account, the Overview's totals for today (tokens, estimated cost, and how many sessions, devices and open alerts) and up to 30 days of daily usage (per day, provider and model: token counts and cost); and files you export from the Overview (CSV and PDF of what the Overview has loaded; in this release, for a signed-in account, that is the totals and daily usage per provider and model) | The totals and daily usage in the app's own database (cli_pulse_cache), which the app does not encrypt; exported files in the app's cache folder; everything else in the app's encrypted preferences (cli_pulse_secure_prefs). Sign Out starts deleting all of it except exported files at once, without waiting for the network, and Delete Account does the same once our server has deleted the account. Exit Demo Mode deletes the encrypted preferences. If, when the app starts, our server does not accept the saved sign-in, the app deletes the encrypted preferences but keeps the totals and daily usage until someone next signs in on the phone and they are fetched again. That happens after you sign out, or delete the account, on another device, and also whenever the saved sign-in, which lasts an hour, has expired and the background sync has not yet renewed it: the start-up check does not renew it, the sync renews it only on its first run after it expires, and Android can put that run off for hours, so opening the app after some time away can sign you out. While the app is open, a refused renewal changes nothing on the phone: it keeps showing you as signed in, unable to connect, until you tap Sign Out or the check above runs again, which happens when the app starts or Android recreates its screen (for example when you rotate the phone). A sign-in code you abandon stays until you start another or sign out. Exported files stay until Android clears the app's cache or you uninstall the app (each of the four kinds of CSV export replaces the previous file of its kind; PDF reports are named by date, so one stays for each day you export one). Uninstalling the app deletes all of it |
❌ No (the one-time code goes to Supabase Auth once, to finish that sign-in) | Shows who is signed in, finishes a browser sign-in, and lets you share an export. This release saves the totals and daily usage but never reads them back |
| What the Android app's libraries keep on the phone: Google's Firebase keeps a random ID for this install of the app, a sign-in token of its own, the notification token, and a record of the days it was used, which it sends to Google with its requests; Google's log uploader may keep events it has not yet sent to Google (Google Play's billing library can add some once you open Settings › Subscription); Sentry keeps its own random installation ID, a running copy of its latest breadcrumbs and device details, and any crash reports not yet sent; Android keeps the background sync's schedule | The app's private storage | ❌ Not to our server, except the notification token (see its row) | Firebase, Google's log uploader and Sentry use them as described under Security practices and Third-party sub-processors. Neither Sign Out nor Delete Account deletes them; uninstalling the app does |
| Git activity metadata (commit hash, keyed hash of the project path, commit timestamp, merge flag) | Supabase — only when the "Track git activity (Yield Score)" toggle is ON, and only collected by the Companion CLI | ✅ Yes (opt-in only) | Powers the Yield Score feature |
| Git commit messages, diffs, file paths, author identity | — | ❌ Never | Explicitly excluded even when Yield Score is on |
| Quota alerts you dismiss | UserDefaults on that device | ❌ Never | Suppression list to prevent re-firing |
| Codex accounts seen with credits (a digest of the ChatGPT account ID, never the ID itself) | UserDefaults (app group) on this device | ❌ Never | Keeps a spent Codex credits balance visible as "0 credits left" |
Codex limit reset credits (since v1.57, Mac app only): how many limit resets OpenAI has granted the ChatGPT account Codex is signed in to on this Mac, and when each expires. The app asks OpenAI for them (a read-only request to chatgpt.com) with the same Codex sign-in, in the same refresh as Codex's quota. It never asks OpenAI to use one |
Kept in memory in the Mac app while it runs, each credit's ID and the ChatGPT account ID only as hashes; never written to disk | ❌ Never, and not to your iPhone or Apple Watch either | Shown on the Codex card |
| Your subscription, if you buy one in the App Store or Google Play reports one for your Google account: the plan, that it is active, when the current period ends, which store, the App Store's product and transaction IDs or Google Play's order ID (or, if Google gives none, the start of the purchase token), and when it was last checked. To check it, the app sends our server the App Store's signed transaction and product ID, or Google Play's purchase token, product ID and the app's package name | Supabase, linked to your CLI Pulse account | ✅ Yes, while signed in | Keeps your plan with your account, so every device shows it; deleted with your account |
| Crash reports from the Mac, iPhone, Apple Watch and Android apps (see Security practices for what they contain) | Sentry (sentry.io), scrubbed before leaving the device | ✅ Yes, when a crash or error happens, whether or not you are signed in; the same SDK also reports each app session's start and whether it ended in a crash | So crashes are visible to us without waiting for an App Store or Google Play review |
| Anonymous install statistics (Mac app only: random install id, install channel, app version, OS major.minor, display language, and a few once-ever yes/no milestones listed below) | Supabase, not linked to any account | ✅ Yes, unless you turn it off | Tells us whether the app actually works for people who never sign in |
| Network details of every request to our server, from every app: your IP address, and the approximate location (city and country) that our hosting provider works out from it and records with the request; the label each request carries, which names the app, or the library or browser making the request, usually with version numbers (for the Mac, iPhone and Apple Watch apps, also the system's); and the address each request asks for, which can include your account's ID | Supabase's request and sign-in logs, kept for about 90 days; with each sign-in, Supabase Auth's record of that session, removed when you delete your account, and when you sign out only if our server accepts the app's sign-out request (the apps send it once, without renewing the sign-in first, so signing out while offline or after the sign-in has expired leaves the record; records of expired sessions are cleaned up later); for each attempt to pair a Mac, our own database, until the cleanup after it is an hour old (at most about a day) | ✅ Yes, with every request | Running and protecting the service (pairing attempts are counted to limit their rate). Deleting your account does not remove log entries already written |
The key point: the two categories of data you'd be most worried about — provider API keys and raw session-log contents — never touch our servers, full stop.
Everything above marked ✅ that is attached to your account is uploaded only while you are signed in, apart from what signing in itself sends (asking for a sign-in code already creates the account), with these exceptions:
The app's own uploads go to the account you are signed in to. The background helper uploads with the pairing this Mac made when you set up background sync. Since v1.55 it uploads only while the app is signed in to that same account: after you switch to another account it uploads nothing until you pair this Mac again, and after you sign out it stops reading altogether (see When the scanning starts below for how that applies right after an update).
The Android app, too, uploads only while you are signed in, and then also in the background, even when the app is closed: about every 15 minutes, while the phone is online and its battery is not low, it asks our server for your Overview totals, and the first time after you sign in it adds the phone to your account. Each request for totals sends the phone's current date (the day only), so that "today" matches your calendar; our server uses it to answer and does not keep it. While the app is open, the screen you are on refreshes from our server every 30 to 60 seconds, and the Alerts tab asks our server, at most every 5 minutes, to work out budget alerts from your daily usage. Three things happen whether or not you are signed in, in Try Demo too: Sentry receives a record of each app session and any crash report (see Crash reports); Google's Firebase registers the app with Google and gets it a notification token when the app starts (Firebase's own default, which our code does not change); and opening Settings › Subscription asks Google Play on the phone which CLI Pulse subscriptions exist and whether you have one. None of these goes to our server, except, while you are signed in, a notification token Firebase hands over or a CLI Pulse subscription Google Play reports, as the table and Third-party sub-processors describe.
Since v1.44 you can use CLI Pulse without an account. That means we cannot tell whether the app works for the people using it that way — whether it found their CLIs, whether they ever saw a number. So the Mac app reports how far it got, and nothing else; each step is recorded once:
If you use remote control, four more facts, each a yes/no recorded once: a phone reached this Mac over your local network; a phone reached it over your own private network; a session was started asking Claude to hand off to claude.ai; and a session driven over remote control was a tool other than Claude.
Sent with each report: a random identifier generated on your Mac, which
channel the app came from (App Store, direct download, or Homebrew), the app
version, your macOS version rounded to major.minor (15.1, never the full
build string), which of the app's six languages it is displaying (or "other"),
and the current state of every fact above. Our server records when it first and
last heard from the install, and when each fact was first reported.
Not sent, ever: your account or email (there is no account involved and no login token is attached to the request), your name, your device's name, serial number or hardware identifiers, any file path or project name, which providers you use (beyond the remote-control fact above), or any token count or cost figure. Your IP address is not stored with the report, though, like any request over the internet, the connection itself reaches our hosting provider, whose request logs keep it, with the city and country it points to, for about 90 days (see Network details in the table).
The identifier is random — not derived from your hardware, and not a hash of anything about it. It is kept in the app's preferences on your Mac. Moving the app to the Trash does not delete those preferences (macOS leaves them behind), so a reinstall reuses the identifier; deleting the preferences deletes it, and the next launch then makes a new, unrelated one. Nothing in the report connects it to you or one identifier to another, and we do not use our hosting provider's logs to try.
Turning it off: Settings → Privacy → "Send anonymous install statistics". Off means nothing is sent at all. Strict privacy mode (called Local-only mode in earlier versions) also turns it off — you do not need to set both. The app tells you about this on first launch, before it sends anything. Neither switch affects crash reports.
┌──────────────────────────┐ ┌──────────────────┐ │ Your Mac │ │ AI provider │ │ ┌──────────────────┐ │ │ (OpenAI, │ │ │ API keys, tokens │─── Direct ───▶│ Anthropic, │ │ │ (Keychain, CLI │ │ HTTPS │ Google, ...) │ │ │ credential files)│ │ └──────────────────┘ │ └──────────────────┘ │ │ ┌──────────────────┐ │ │ │ JSONL session │ │ (read on the Mac only, never sent) │ │ logs │ │ │ └──────────────────┘ │ ┌──────────────────┐ │ ┌──────────────────┐ │ │ CLI Pulse │ │ │ Daily usage, │ │ │ Supabase backend │ │ │ quotas, running │─── HTTPS ────▶│ (your account) │ │ │ AI CLI sessions, │ │ └────────┬─────────┘ │ │ alerts, device │ │ │ │ │ readings │ │ ▼ │ └──────────────────┘ │ ┌──────────────────┐ │ │ │ Your iPhone / │ │ │ │ Apple Watch / │ │ │ │ Android phone │ │ │ └──────────────────┘ │ ┌──────────────────┐ │ ┌──────────────────┐ │ │ Install │─── HTTPS ────▶│ Anonymous install│ │ │ milestones │ │ │ table: NO account│ │ │ (opt-out) │ │ └──────────────────┘ │ └──────────────────┘ │ ┌──────────────────┐ │ ┌──────────────────┐ │ │ Sentry │ │ │ Crash reports │─── HTTPS ────▶│ (crash reports) │ │ └──────────────────┘ │ └──────────────────┘ └──────────────────────────┘
The scanner runs entirely on your Mac. Your API key never passes through our servers — it goes directly from your Mac to the provider.
The Android app runs no scanner and holds no provider keys. It reads what your account holds and sends back the changes you make in it: settings, alert states, a webhook and its test, and your linked sign-in methods. It also sends the record of the phone, sometimes its notification token, and requests for our server to work out budget alerts. It sends its crash reports to Sentry, registers with Google's Firebase for its notification token, and asks Google Play about subscriptions when you open Settings › Subscription; if Google Play reports one, the app sends our server its purchase token, product ID and the app's package name, for checking.
(Added in v1.50. It corrects something this page previously left implied, and the correction is a change to the app, not only to the wording here.)
Everything above describes what CLI Pulse does once it is running. It did not say clearly enough when that begins, and until v1.50 the answer was: as soon as you chose to use the app without an account, which you could do from the very first screen of the setup wizard — two screens before the one explaining what gets read. We found this by testing a fresh install and watching what it did.
Since v1.50, choosing to use CLI Pulse without an account asks a separate question before anything is read. Until you answer, the app — and, since v1.55, its background helper — reads none of your session logs or credential files, contacts no AI provider, and reads no Keychain item except CLI Pulse's own. Since v1.55 the question has three answers: "Start local scan", "Last 30 days only" and "Not now" (see the next section for why). "Not now" is remembered, is not overridden by signing in later, and is reversible from Settings → Privacy at any time: without an account, with the scan switch; while signed in, with "Choose again…", which shows the same question with its three answers.
Versions 1.50 through 1.54 did not keep that promise on a Mac synced to your account, although this page made it from v1.50. There the app never checked the answer: after "Not now" it kept reading this Mac and syncing what it found anyway, as if you had started the scan. It read your session logs, including the one-time read of older logs if that had not yet run on the Mac; it read the credentials your AI CLIs keep and asked their providers for your quota; and it uploaded what it found, such as that quota and your daily usage, to your account. Without an account, and on a signed-in Mac that was not synced, the app honored "Not now". Since v1.55 it honors it on a synced Mac too, where it still shows what your account already holds. "Choose again…" is also new in v1.55; before, the only way to change the answer while signed in was to sign out.
Starting the scan turns on:
~/.codex/sessions/, ~/.codex/archived_sessions/, ~/.claude/projects/ and
~/.config/claude/projects/. Every refresh uses the last 30 days: a log last
written before then is not opened, and older lines in a log that is still in
use are skipped, not kept. Only if you allow it, CLI Pulse also reads up to a
year of older logs to fill in your usage history: once, and the Codex logs
again whenever a CLI Pulse update changes how it counts Codex usage, or when
the account you're signed in to has older Codex figures from this Mac to
correct (see Reading more than 30 days back). Parsed on your Mac for usage
records; the results are cached on your Mac./private/tmp for the checkout it came from, and how
the usage panel's Sessions list adds up the last 30 days by session, a
session's subagents included, named by its project folder. They stay on your
Mac; of what the logs give, only the daily numbers in the table sync, and only
when you are signed in..git) in the folders it names. What it keeps from
that, and what syncs while you are signed in, is the AI CLI sessions running
on your Mac row in the table.~/.codex/auth.json). CLI Pulse has always done this; it is now
disclosed, and v1.50 also fixed a bug that made it happen far more often than
intended — a date-parsing error meant a check written as "renew if the token is
more than 8 days old" renewed it on essentially every refresh.Signing in implies consent to the 30-day scan, because the sign-in step comes after the wizard's privacy screen and because syncing to an account is a larger commitment than scanning locally. It does not imply consent to reading older logs: see the next section. While you are signed in, the 30-day scan has no switch of its own; signing out stops it, in the app and in its background helper, whatever you answered. Signing out does not revoke the consent, though: if you then use CLI Pulse without an account, an earlier "Start local scan" or "Last 30 days only" applies again (the scan runs on this Mac, and nothing is uploaded), and the Settings toggle turns it off.
~/.claude/settings.json, to check whether CLI Pulse's
approval hook is installed, and the Companion CLI, asked which sessions to
list there, reads the command lines of the running programs to find AI CLI
sessions. After "Not now" neither happens, and the tab lists only the
sessions CLI Pulse started (Companion CLI 1.30.0 and earlier still read the
running programs).~/.claude/.credentials.json to sign the session in, and, when that token
has expired, renew it and rewrite the file. And the app contacts services other than the AI providers' own APIs: our server
(for anonymous install statistics, and for your account if you are signed in),
Sentry for crash reports, GitHub to check for updates, the providers' public
status pages (unless you turn off Settings › Check provider status), and an
exchange-rate
service (see Third-party sub-processors).(Added in v1.55. Like the section above, it corrects this page, and the correction is a change to the app.)
The question above said "Session logs, last 30 days" from v1.50 to v1.54, and this page said the same. That was incomplete. Ever since CLI Pulse has kept a usage history (the year-long heatmap and the Usage Dashboard), the first successful scan on a Mac has also read up to a year of older session logs, once, to fill that history in. It did this for everyone whose scan was running, including people who had just agreed to "30 days". The history it built stayed on the Mac: the one-time read was not uploaded.
Since v1.55 that read is its own question:
The routine scan had a smaller gap of the same kind. On its first run, and whenever its cache was reset, it opened Claude Code logs of any age and discarded their lines older than 30 days without using them. Since v1.55 a log last written before the 30-day window is not opened.
(Added in v1.55. Earlier versions of this page described the app and its built-in helper only, and the promises above do not all hold for this program.)
The Companion CLI is a separate program you can install from Settings ›
Companion CLI. It runs in the background whenever you are logged in to your
Mac, is not sandboxed, and keeps its own pairing with your account in
~/.cli-pulse-helper.json.
While it is paired and allowed to (see below), every 2 minutes by default it:
~/.codex/auth.json to report whether
Codex is signed in with a subscription or an API key;~/.codex/auth.json,
~/.gemini/oauth_creds.json) to ask it for your quota, uploads the quota
figures, and renews an expired Gemini token, which rewrites
~/.gemini/oauth_creds.json;claude /usage;~/.clipulse/claude_session.json and to CLI Pulse's app-group folder,
where the app can use it (not in Strict privacy mode: see below);git log in the project
folders of running sessions and uploads the commit metadata listed in the
table.While it is paired it also asks our server about once a second whether a
remote-control or machine-control request is waiting for this Mac (sending only
its own device identifier and secret). Releases after 1.30.0 ask only while
they are allowed to upload (see below); 1.30.0 and earlier ask whatever the app
says. Whatever its state, it answers the CLI Pulse app on this Mac when the app
asks it something, such as the Machine view's readings; while your answer
allows the scan, with or without an account, that answer can include whether
Codex is signed in with a subscription, which it reads from ~/.codex/auth.json,
and the AI CLI sessions it finds among the running programs, for the Sessions
tab. Releases after 1.30.0 read neither while the answer pauses the scan.
It also runs the AI CLI sessions you start through it from the app's Sessions tab. While it is allowed to upload (see below), it registers each one with our server, with its program, the name of its project folder and its label, and posts what the session prints, with secrets redacted, its status and short notes about it, such as an exit code. Our server accepts these only while "Mac control requests from your other devices" is on for the paired account, and no CLI Pulse app shows them any more.
Which versions follow your answer. Companion CLI 1.30.0 and earlier versions do not read the app's answer, its sign-in or its Privacy switches at all: while installed and paired they keep collecting and uploading, to the account they were paired with, whatever you choose in the app, including after you sign out. Companion CLI 1.31.0, released with CLI Pulse 1.55, and later releases follow them:
claude /usage.To stop it entirely: Settings › Companion CLI › Uninstall….
com.apple.security.app-sandbox), for the app and its background helper;
there, file access outside the app container requires security-scoped
bookmarks you grant explicitly in Settings → Advanced → CLI Tool Access. The
direct-download build, its background helper and built-in agent, and the
Companion CLI are not sandboxed: they read the files named in this page
directly.android:allowBackup="false"). On
Android 12 and later a direct transfer to a new phone can still copy the
app's files. Android does not move Keystore keys, so the copied sign-in tokens
cannot be read there, but the saved totals and daily usage can. Exported files
are in the app's cache folder, which Android does not copy.~/Library/Containers/yyh.CLI-Pulse/Data/Library/Caches/yyh.CLI-Pulse and
~/Library/Containers/yyh.CLI-Pulse.helper/Data/Library/Caches/yyh.CLI-Pulse.helper;
for the direct-download build, ~/Library/Caches/yyh.CLI-Pulse and
~/Library/Caches/yyh.CLI-Pulse.helper.sendDefaultPii = false), and a local beforeSend hook scrubs
JWTs, strings shaped like sk-… API keys, Bearer headers, /Users/<name>
paths (in the Apple apps), and any field whose name contains common sensitive fragments (token,
secret, password, api_key, supabase, etc.) before the event leaves your
device; it also clears the report's IP-address field. A report carries the
stack trace, the app and OS version, the device model and the other device
details the SDK attaches, and up to 50 recent "breadcrumbs" (steps the app
took before the error). In the Mac, iPhone and Apple Watch apps a breadcrumb
for a web request the app made keeps its method, status, sizes and timing,
and its address without the query string or fragment; since v1.55 any part
of that address that looks like an identifier (an account, organization or
session ID, a long number, an email address), and the part after a word such
as workspace, organizations or users, is replaced before the report
leaves your device, and so are UUIDs and email addresses anywhere else in
the report's breadcrumbs and error messages. This works by the shape and
place of each part, so a part that looks like an ordinary word and follows
no such word is kept. (Before v1.55 the query string was sent too, and a
request to our server named your account's internal ID in it.) The Android app records no web requests, and its hook does not look for
identifiers, UUIDs or email addresses: it removes only the secrets named
above, and it checks field names only in a report's tags, extras and
breadcrumb data. Its breadcrumbs come from the Sentry SDK's own defaults: the
app's screens opening and closing, the app moving to the background, and
changes on the phone such as the network connection, the battery, the screen
turning on or off, and the time zone or language. By the same defaults it
sends a report, with the stack trace, when the app has not responded for about
five seconds (what Android calls "not responding"). Android reports carry no
screenshots.
The SDK also reports, for each app session, that it started
and whether it ended in a crash, with a random installation identifier the SDK
generates. Every report from the Mac and iPhone apps also carries a one-way
hash the SDK works out from the device and the app (on an iPhone from the
identifier iOS gives all apps from one maker, on a Mac from its network
hardware address); on a Mac it stays the same if you reinstall the app, and
so, usually, does the installation identifier, which the SDK keeps in a
folder a reinstall leaves in place. In the iPhone app the breadcrumbs also name the
screens you opened, by their titles: the screen for a paired Mac is titled
with the Mac's name, which often contains your own name, and a session's
screen with the session's title, which can name its project folder (for a
session the Companion CLI reported, a folder name taken from its command
line). With
Settings › Hide personal information on, these titles are masked. So crash
reports carry no account ID, but they are not anonymous, and the App Store
listing counts them as linked to you. In the Mac and iPhone apps the SDK also
sends a report, with the stack trace and breadcrumbs, when the app has frozen
(its main thread has not responded) for about two seconds (three on the Mac),
and the iPhone app sends one, with the breadcrumbs, on the launch after iOS
ended it while it was open. Since v1.59 the Mac app also sends an error report when three
refreshes in a row have failed (a request to our server failed, or a
refresh had not finished after ten minutes), at most one a day. Besides
what every report carries (the stack trace, the app and OS version, the
device details, the device hash described above and the SDK's random
installation identifier), it holds
only a fixed word for the reason (such as rejected or network_failed),
the HTTP status, which of the app's two refresh paths it was and how many
refreshes in a row had failed. It has no breadcrumbs. When pairing this Mac
with your account fails, the Mac app also sends a report that adds only an
error code (a fixed code, or the system's error domain and number) to what
every report carries; unlike the reports around it, this one keeps the usual
breadcrumbs. Since v1.60 the Mac app also sends a separate report of its own
when its main thread has not answered for ten
seconds, at most two a launch. Besides what every report carries (the app
and OS version, the device details, the device hash described above and the
SDK's random installation identifier), it holds only the number of seconds, a fixed word for what the
main thread was doing (running, waiting, stopped, uninterruptible, halted or
unknown), how many times it was suspended, how much processor time it used
while we looked, a fixed word for the kind of event loop it was in
(default, event_tracking, modal_panel, none, unreadable or
other), which install channel this Mac has, and whether it was waiting in
a loop that waits for you on purpose (a menu held open or a dialog). It has no stack trace and no breadcrumbs, and no
window title, file name or account detail. Since v1.62 the Mac app outside
the App Store also sends an error report when, on a refresh you started, the
Keychain answered "cancelled" or "authentication failed" for a browser's
"Safe Storage" item, and when a background refresh was told by the Keychain
that reading such an item would need a dialog (which a locked keychain also
says); and the iPhone app sends one when the Keychain did not accept the new
sign-in tokens and the app kept them in memory, when it wrote them later,
and when it opened and found that its last run had ended still holding
some. Each of these outcomes is sent at most once a UTC day. Besides what
every report carries (the stack trace, the app and OS version, the device
details, the device hash described above and the SDK's random installation
identifier), each holds only a
fixed word for what happened and, for the Mac one, the browser's name from a
fixed list, the install channel and whether the screen was locked (when the system says). It has
no breadcrumbs, no tokens, no error text from the system, and no account
detail. Performance tracing
is disabled
(tracesSampleRate = 0). There is no switch to turn crash reporting off.~/Library/Group Containers/group.yyh.CLI-Pulse)
until that folder is deleted.In an app: Settings → Delete Account, on the Mac, iPhone or Android, while signed in (see Your controls for what the Android app then removes from the phone).
Without an app, for example after you uninstalled it, or if someone asked for a sign-in code for your address: email clipulse.support@gmail.com from the address your account uses, with the subject "Delete my CLI Pulse account". We may reply to that address to confirm the request is yours, and then delete the account as the app would; if the request cannot be confirmed as yours, the account is not deleted. If you signed in with Apple and hid your email address, write from any address, tell us the relay address, and we will agree with you how to confirm the request.
Either way, deleting the account removes your sign-in, settings, alert webhook, usage history, quotas, sessions, alerts, device records and the other records linked to your account, within 30 days (see Data retention); a team you own passes to another member, if one remains. It does not remove what is stored apart from your account (anonymous install statistics, crash reports already sent to Sentry, the Firebase registration Google keeps for an Android install, and invitations to someone else's team sent to your email address), nor entries already written to Supabase's request and sign-in logs, which are deleted after about 90 days, nor the emails you sent us, including the request, which stay in our mailbox. Nor does it reach copies on your devices: an Android phone that was signed in keeps what it saved. It deletes its sign-in details the next time its start-up check is refused (not while it is offline), and keeps the usage figures until a later sign-in replaces them, you sign out there, or you uninstall the app. Until its start-up check deletes the sign-in details, its background sync keeps trying the old sign-in with our server each time Android runs it, and our server refuses it.
Your login email is collected to authenticate you. We may also use it, rarely, to contact you about CLI Pulse itself:
What this is not, stated as commitments rather than intentions:
Every product email carries a one-click opt out, honoured immediately and permanently. If you would rather not receive any, opt out of the first one and that is the end of it.
If a survey ever offers something in return for your time, that offer is unconditional on what you say. We will not ask you to review the app in exchange for anything — the App Store and Google Play forbid it, and so do we.
commits, sessions, session_commit_links,
daily_usage_metrics, yield_score_daily). 18 months is long enough to
support year-over-year cost comparisons with one month of buffer; beyond that
the historical detail adds no product value and we'd rather delete it.We do not share data with any party not listed above, except where you direct it: the AI providers you use receive the requests CLI Pulse makes to them with your own credentials, and an alert webhook you add receives your alerts. CLI Pulse also fetches exchange rates from open.er-api.com, at most once a day, to show costs in other currencies; that request carries nothing about you or your usage. The Mac and iPhone apps also ask the public status pages of the providers you track (such as status.claude.com or status.openai.com) whether a provider has an outage, unless you turn off Settings › Check provider status. Those requests carry no credentials and none of your usage, but which pages are asked shows which providers you track, and, like any web request, they show your IP address. We do not sell data.
CLI Pulse is a developer tool intended for users 17 or older. We do not knowingly collect data from children.
We will update the "Last Updated" date above when this policy changes and note material changes in release notes. The authoritative version lives at https://cli-pulse.github.io/cli-pulse/privacy.html (this page).
We've looked at adding E2EE to the Supabase-stored metrics. Today, the most sensitive data (API keys, session log contents) already never reaches our servers, so the marginal privacy gain from encrypting what we do store is smaller than it sounds. Implementing E2EE also conflicts with cross-device sync, which requires multi-device key management we don't want to ship half-built. If you have a specific threat model where E2EE on metrics matters to you, please open an issue — we'd rather hear the use case than guess.
[CLI Pulse Security])