FAQ
Common questions about privacy, browser support, data access, platforms, and troubleshooting.
Is this a keylogger?
No. Keystroke content is not captured unless you opt in (text_content = true); by default only the fact of typing and the field type are recorded. If capture.text_content is not yet set, an interactive background start asks you once, defaulting to no (the exact conditions are in the CLI reference), and zanei config set capture.text_content <true|false> changes it at any time. Password fields are excluded even if you opt in. Enabled redactors replace only matching patterns and do not remove names, phone or fax numbers, or postal addresses; use capture-time filters and retention to limit exposure. Data does not leave your machine. See the privacy model.
Where does my data go?
Into one local SQLite file (~/.local/state/zanei/store.sqlite) and nowhere else. The file is encrypted, and its key lives in your login Keychain. The binary has no feature that transmits data: no endpoint, no telemetry, no account. Data is deleted automatically after 48 hours by default.
Does it take screenshots?
No. Zanei does not use screenshots, OCR, screen recording, or screen-capture APIs. If you separately enable capture.content_snapshot, it reads text that macOS Accessibility exposes in the visible part of the frontmost window. It does not read secure-field subtrees or single-line input values, and takes no snapshot while Secure Input is active. See the privacy model.
Which apps can it read content snapshots from?
Only apps that expose useful text through macOS Accessibility. Coverage varies by app and UI implementation; zanei apps lists candidates to choose from, but does not guarantee that an app exposes readable text. Snapshots cover the visible part of the frontmost window, not a document’s complete contents. Safari, Firefox, Brave, Edge, Vivaldi, and Arc are excluded from snapshots by default because their private windows cannot be identified reliably. Website filters apply to Chrome and Safari. Other browsers can be controlled by app scope.
Which browsers can it capture URLs from?
Chrome and Safari are supported for both standalone and embedded recording. Chrome’s window mode lets Zanei omit Incognito URL events and text-content bodies. Safari reports an unknown privacy mode, so private-window exclusion is not guaranteed. Firefox URL capture is not implemented. Details and the per-browser table: event reference.
How do I keep an app or site out of the record?
zanei filter exclude-app add com.example.app
zanei filter exclude-site add example.com
These are capture-time filters: matching events are discarded before being written, so there is nothing to clean up afterwards. Password managers are excluded by default. See filters.
I opted in — does typed text get captured in Electron and other Chromium apps?
Usually yes, in Electron apps such as Claude, Slack, and VS Code, and in Chromium-based apps that
are not Electron. These apps build their accessibility tree only once an assistive client asks for
it, so their input fields would otherwise report as unknown to macOS Accessibility. Zanei reads
the application element’s role when it attaches its observer, which is the signal Chromium builds
its tree on, and reads the focused element again a second later because the tree appears
asynchronously. While a content opt-in is on, it also tries to set AXManualAccessibility on every
app the filters allow for that opt-in; Electron apps build their tree in response, and apps that do
not support the attribute report it as unsupported and are unaffected.
Eligible typed text is then captured like anywhere else. Zanei never captures a field it cannot
classify, which keeps password fields out. Restart Zanei after changing capture.text_content.
Why are typed text and content snapshots missing in some Chromium-based apps?
A Chromium-based app can be built to ignore accessibility requests from external clients, and some
apps disable the integration outright. If an app does not expose its content tree, typed-text bodies
(ui.value / data.text) and content snapshots (content.snapshot) for that content are missing,
and Zanei does not use screen images as a substitute. Keystroke facts (such as
input.key counts) are still recorded. Clicks are recorded only to the extent AX hit-testing works.
Clipboard copy and paste events are still recorded when their bodies are omitted. For copy, the body
comes directly from the macOS pasteboard and can be recorded only when capture.text_content is
enabled and the source window is allowed for text capture; this path does not require an AX tree. For
paste, those text-capture conditions apply to the destination window and AX must also classify the
destination as a text field. If it cannot, the paste event is recorded with a null body.
Can I read the store directly?
The live store is encrypted with SQLCipher (AES-256). The key lives in your login Keychain as “Zanei store key” and never leaves this Mac, so a copy of the file in a backup, a sync folder, or on another machine cannot be read without it.
To look inside with your own tools, export a plain SQLite snapshot:
zanei export --format sqlite --since 24h --out snapshot.sqlite
It has the same tables as the live store and opens in sqlite3, DB Browser for SQLite, any language’s SQLite bindings, and zanei query --store snapshot.sqlite. The snapshot is not encrypted; treat it like any other export. It includes content-snapshot bodies when those events are in scope. For sharing or external processing without them, select only the other families, for example --types app.*,window.*,ui.*,input.*,browser.*,clipboard.*. For most purposes zanei query --format jsonl is still easier and keeps you on the stable event envelope rather than internal table layouts, which may change between versions.
How is this different from ChatGPT’s Computer History?
The idea is the same: your activity becomes context for an AI. The architecture is different. Zanei is open source, stores data locally only, does no cloud processing, and is not tied to one assistant (CLI / skill / MCP). It stops at structured, LLM-ready data and leaves interpretation to whatever agent you connect.
Does it work on Windows or Linux?
Not yet. The event schema is OS-independent by design, and collectors for Windows (UI Automation) and Linux (AT-SPI2/evdev, X11 first) are on the roadmap. Today, Zanei is macOS only.
Can I use it on a work machine?
Zanei records your own activity on your own machine. If the machine is employer-managed, follow your organization’s policies. Using it to monitor other people is out of scope and may be illegal in your jurisdiction.
I granted a permission but doctor still says denied
Toggling a permission sometimes requires the recorded process to restart before macOS reports it correctly. Run zanei stop && zanei start, then zanei doctor again. After tccutil reset, tccd may suppress permission dialogs until macOS is restarted; this behavior has been confirmed on physical hardware. If you built from source, remember that permissions bind to the signing identity, so a rebuilt binary may need re-granting (see permissions).
zanei start exits with code 3. Is the recorder running?
Yes. A background start returns code 3 only after the recorder is alive and its heartbeat reports missing permissions. Accessibility adds the bundled app’s Zanei row automatically, while Input Monitoring may omit it even after the dialog grant takes effect. Restart, then trust the recorder-reported zanei doctor result. To manage an omitted row from the list, use + and select the installed Zanei.app; while the permission is still reported missing, zanei doctor --fix opens the pane and copies that exact path. See the permissions guide. The daemon stays alive while permission-dependent collectors are degraded. While the first-run permission dialogs are awaiting a response, zanei doctor may report Automation as not_determined; respond to the dialogs, then run it again. The text-content choice is not shown on this missing-permission path; after granting permissions, run zanei stop && zanei start and the first successful interactive start asks you to choose.
How do I uninstall Zanei?
Use this order so the bundle-specific permission reset can still resolve the installed app:
- Remove the Accessibility and Input Monitoring grants. Delete the
Zaneirows in System Settings → Privacy & Security, or run these commands while Zanei is still installed:
tccutil reset Accessibility dev.zanei.recorder
tccutil reset ListenEvent dev.zanei.recorder
- Stop the recorder:
zanei stop
- Uninstall Zanei:
brew uninstall zanei
- Homebrew leaves your configuration and recorded data in place. To delete them too:
rm -rf ~/.config/zanei ~/.local/state/zanei
- Remove the store key from your login Keychain. After this, no backup copy of the store can be read:
security delete-generic-password -s dev.zanei.store
Or delete “Zanei store key” in Keychain Access.
After uninstalling, the bundle-specific tccutil commands cannot resolve dev.zanei.recorder. TCC matches grants by bundle ID and signature, so a grant left behind can reappear when the same app is reinstalled. To remove a remaining Accessibility decision completely, delete a lingering Zanei row in System Settings, or run tccutil reset Accessibility without a bundle ID. The service-wide command resets Accessibility decisions for every app.
If you forget to run zanei stop before uninstalling, the recorder detects that its own executable is gone and shuts itself down automatically within tens of seconds.
After brew upgrade, run zanei stop && zanei start to switch the recorder to the new version. If the old recorder already shut itself down and stop reports that it is not running, run zanei start instead.
What happens to my data after upgrading?
Zanei 0.3.0 and later encrypt the store. A store written by 0.2.x or earlier is plaintext SQLite, and the recorder never rewrites such a file. On the first zanei start after upgrading it renames the old store to store.sqlite.plaintext-<timestamp> next to the new, encrypted store.sqlite, creates that new store at schema version 7, and logs zanei: kept the previous plaintext store as …. Every read (zanei query, zanei timeline, zanei export, and the MCP server) returns the events from both files as one history, so nothing recorded before the upgrade disappears from your timeline. zanei status lists the set-aside file under store.retired_plaintext.
The set-aside file stays plaintext. It only holds events older than the moment it was set aside, so the recorder deletes it as soon as that moment leaves the retention window (output.retention_hours, 48 hours by default); a longer retention keeps it longer, a shorter one removes it sooner. Until then, events inside it that leave the window are purged from it on the same schedule as the live store. An active zanei pause and the event counters carry over to the new store. zanei purge --all deletes it immediately, and so does deleting the file yourself. Upgrading an encrypted 0.3.x store to 0.4.0 migrates it in place to schema version 7, preserving events and daemon state while discarding only the permission snapshot that the recorder can probe again.
Store migrations are forward-only. If you may need to roll back, keep a backup from before the upgrade. Restore that backup when rolling back; never change the schema version metadata by hand.
zanei start fails with Bootstrap failed: 5: Input/output error
If zanei start reports error: daemon operation failed: /bin/launchctl failed while attempting to bootstrap the Zanei launch agent with exit status: 5: Bootstrap failed: 5: Input/output error, run zanei stop, wait for it to finish, and then run zanei start again.
My timeline is empty
Check in this order:
zanei status— is itrunning? If not:zanei start.zanei doctor— are permissions OK? (Missing permissions exit with code 3.)- Is the range right?
timelinedefaults to the last hour; try--since 24h. - Are filters in allow-list mode?
zanei filter showdisplays the active mode.
status reports store_corrupt
Stop the recorder, preserve the damaged file under another name, and start with a new store:
zanei stop
mv ~/.local/state/zanei/store.sqlite ~/.local/state/zanei/store.sqlite.corrupt
zanei start
Replace the path when using --store. This does not repair or delete the moved file; it starts a new empty store.
store_locked means the store is encrypted but the key in your login Keychain is missing or does not decrypt it. Recover the same way: the new store uses the existing key, or a new key if none exists. Losing the key loses at most the retention window (48 hours by default) of data. If the message says your login Keychain is locked, do not move the store: unlock the Keychain (for example by opening Keychain Access) and try again. zanei doctor shows the key state on its Store key: line.
Text content is not captured
Text content is recorded only while the opt-in is enabled. Check zanei status; if TEXT CONTENT is off, enable it explicitly and restart recording:
zanei config set capture.text_content true
zanei stop && zanei start
In 0.3.0, Safari, Firefox, Brave, Edge, Vivaldi, and Arc are outside the default text-content scope even for upgraded configurations. Their body fields stay null while events and non-content facts remain. Run zanei filter show before changing those defaults.
If Chrome body fields remain null while content capture is enabled, run zanei doctor and inspect COLLECTOR HEALTH. A current chrome reason identifies an active collector problem; COLLECTOR FAILURES alone is only cumulative history. See diagnosing recorder problems for the persistent daemon logs and recovery rules.
If content is enabled but still missing, run a short foreground session with privacy-safe diagnostics enabled:
zanei stop
ZANEI_TRACE=1 zanei start --foreground 2> ~/zanei-trace.log
Type for a few tens of seconds, press Ctrl-C, and inspect ~/zanei-trace.log. The trace never includes text content; it records only diagnostic metadata such as field class, value length, and capture decisions.
Something else?
Open an issue. Bug reports and design discussion are welcome while the interface is being finalized.