Client
The Watcher client is a single watcher binary
installed on each developer machine. It hooks into the
supported coding agents (Claude Code, Codex) to
record sessions, upload
them to your Watcher deployment, and enforce
blocking review. This page
covers installing it, where its settings live, the command reference, and
diagnosing problems with watcher doctor. To run the client inside a container
alongside a coding agent, see Containers, or
Dev containers for a dev container that reuses the sign-in
already on the developer's machine. For an untrusted workspace, review the
dev container hardening options.
Install
The client runs on macOS (Apple Silicon) and x86_64 Linux. Install it with the published installer, telling it which backend this machine reports to:
# Apollo's hosted cloud
curl -fsSL https://github.com/ApolloResearch/watcher-bin/releases/latest/download/install.sh | bash -s -- --cloud
# A backend your organization hosts (see Self-hosting)
curl -fsSL https://github.com/ApolloResearch/watcher-bin/releases/latest/download/install.sh | bash -s -- --watcher-api-url https://watcher.example.com
The installer reads the chosen backend's version and installs the client
release matching the lower of that server or the installer itself. It
verifies the release checksum, installs the binary as ~/.local/bin/watcher,
records the chosen backend, and registers the agent hooks. The one consent
prompt is for the Codex
config.toml changes described on
Supported agents; hook entries themselves,
including Claude Code's, are installed without prompting. If ~/.local/bin
is not on your PATH, the installer prints
the command that adds it. Open a new terminal afterwards so watcher is
found.
Client and server are mutually compatible within one minor version of each
other (while on 0.x). The latest installer automatically caps the client at
the configured server's version. To impose a lower cap, use that release's
installer:
curl -fsSL https://github.com/ApolloResearch/watcher-bin/releases/download/v<version>/install.sh | bash -s -- --watcher-api-url https://watcher.example.com
To update, run watcher update. The command reads the configured server's
version and installs that release in either direction. It keeps your sign-in
and settings. A packaged client running
through its login agent invokes the same reconciliation automatically every
two to two-and-a-half hours. Foreground and mirror-backed clients remain
externally managed. The installer refuses to overwrite an existing installation.
Clients from releases that predate watcher update must uninstall and
reinstall once to move onto this update flow.
To remove everything (hooks, autostart, and the binary), run watcher uninstall.
To move back to a specific release during a server rollback, run
watcher downgrade --version <version>. The requested version must be lower
than the installed client and must match the configured server. Releases
predating watcher update are unsupported downgrade targets.
First run and sign-in
Run watcher with no arguments to start the client's local services. The
first such run installs the login agent and turns start-on-login on, then
starts in the background and returns control to the terminal. It restarts
after a crash and starts again when you log in. The local UI is at
http://localhost:8228 (or the port set by WATCHER_BACKEND_PORT), where
escalated tool calls wait for approval and per-machine settings are edited.
Use watcher --foreground to keep it attached to the current terminal
without enabling start-on-login.
Later runs read the start-on-login setting rather than turning it back on,
so watcher autostart disable holds. While it is off, watcher starts in
the current terminal and says so.
Any run that starts the client in the current terminal first stops a client
the login agent is already running on that same port, so the terminal one can
take it. The login agent stays installed, and it starts the client again at
your next login unless start-on-login is off. A terminal pointed at some other
port with WATCHER_BACKEND_PORT leaves the background client alone, since that
client is not the one holding the port being asked for.
How you sign in depends on your deployment's authentication mode, which the client discovers from the backend:
- SSO deployments (Apollo cloud, and self-hosted in SSO mode): sign in
once from the local UI (the same address as above), which hands off to your
identity provider in the browser. Starting
watcherdoes not open the browser for you; on macOS the menu bar icon shows a "Sign in" entry until you have signed in. Recording starts once you have, and the running client keeps the credentials refreshed. - Header-proxy deployments: the client attaches headers taken from
WATCHER_HEADER_*environment variables, which must be set in the environment that runs the installer (orwatcher hooks install); there is no browser sign-in. Setup captures the values into a per-deployment file that later launches read, so they persist until you re-runwatcher hooks installwith new values, or remove the variables from the environment and runwatcher hooks clear-headers(the command refuses while the variables are still set, because the next launch would re-capture them). See self-hosting.
Agent hooks
watcher hooks install (run by the installer, and idempotent to re-run)
registers Watcher with every supported agent on the machine. What the hooks
do in each agent, the consent involved, and how blocking review coverage
differs between agents is covered on
Supported agents.
Hooks stay registered until you run watcher hooks remove or
watcher uninstall; they are not tied to the client running. A re-run of
watcher hooks install also repairs hooks that a doctor check reports as out
of date.
How sessions reach your deployment
Recorded sessions do not stay on the machine. The hooks upload the conversation directly to the backend recorded at install time: after tool calls and at the end of each agent turn, so a session is visible in the Analyzer while it is still running. There is no periodic batch export to wait for.
Which backend that is, is a per-install setting:
watcher target set --url https://watcher.example.com # your own deployment
watcher target set --cloud # Apollo's hosted cloud
watcher build-info prints the version and the backend target a launch would
resolve, and watcher doctor (below) verifies the target is reachable and
accepting this machine's requests.
Per-machine settings
Blocking review resolves from settings on each machine: which
graders run, the per-tool flag and deny thresholds,
and the enforcement mode.
Admins can distribute and lock these org-wide with
managed client settings
instead of configuring every machine by hand. The concept pages explain what
each setting does; on the machine they live in layered YAML files under
~/.apollo_monitor/:
settings.yamlis the admin baseline. Values set here apply machine-wide, and the enforcement mode is read only from this file. On deployments that distribute managed client settings via Watcher, the running client pulls the org's document every few minutes and replaces this file with it, so hand edits do not stick; otherwise an administrator or an MDM profile writes it. Admins can lock individual values so local edits cannot override them.settings.local.yamlholds the developer's own overrides. Most settings, thresholds included, are edited from the local Watcher UI rather than by hand.- Each settings profile is an entry under
profilesin whichever of those two files defines it, so a profile's admin values live insettings.yaml. A deployment may still have an admin-managedsettings.<profile>.yamlbeside them; that form is read but deprecated, andwatcher doctorsays where its contents belong. A developer's own edits made while a non-default profile is active still land insettings.<profile>.local.yaml.
A settings profile is a named bundle of these review settings that can be
switched as a unit. The client ships with Default, Auto, Human in the Loop, and
Skip Permissions profiles, and a deployment can add its own. watcher profile list shows what is available, watcher profile current the active one, and
watcher profile set <name> switches; it accepts a profile's id, its display
name, or any of its alternative names. Human in the Loop's id is permissive;
you can also write human-in-the-loop. Both work in a settings file's
active_profile, though a profile an organization pins for every machine is
named there by its id.
Beyond settings, ~/.apollo_monitor/ holds the client's other local state:
a local database of sessions and pending decisions backing the live UI,
client logs, and the sign-in credential and auth-header files, written with
owner-only (0600) permissions. watcher uninstall intentionally keeps this
directory and prints a reminder; remove it manually to wipe a machine's
local Watcher data (nothing already uploaded to the deployment is
affected).
Environment variables
Almost everything about the client is configured through the local UI or the settings files above. A few things are not, because they have to be in place before the client can read a settings file at all: which backend to talk to, which port to listen on, and how to trust your TLS.
Where you set them decides whether they take effect. The agent hooks run
as short-lived subprocesses that the coding agent spawns, and they inherit
nothing from your shell. A variable reaches them only because
watcher hooks install bakes it onto the hook command lines as it writes
them. So for anything in the first group below, set the variable in the
environment that runs the installer (or watcher hooks install): your
shell's startup file, or ~/.config/environment.d/watcher.conf on Linux.
Re-run watcher hooks install after you change it. Changing the variable
alone leaves the already-installed hooks on the old value.
Read by the hooks, so they must be set when hooks are installed:
| Variable | Default | What it does |
|---|---|---|
WATCHER_API_URL | the recorded target | The backend this machine reports to. Overrides whatever watcher target set recorded, both for this launch and for the hooks installed by it. A value with no scheme is read as https://. Prefer watcher target set for a lasting change; use this to point one launch somewhere else. |
WATCHER_BACKEND_PORT | 8228 | The port the local services listen on, which is also the port the local UI is served from. Change it when something else on the machine already owns 8228. |
SSL_CERT_FILE | the system trust store | Path to a CA bundle, for a deployment behind an internal CA or a self-signed certificate. Forwarded to the hooks only when it is set. |
WATCHER_API_KEY | none | An organization API key for the machine to authenticate with, instead of a browser sign-in. Mainly for unattended installs such as containers; a developer machine signs in instead. Unlike the variables above it is not baked onto the hook command lines, so it must be set in the environment the agent runs in, where the hooks inherit it. |
WATCHER_HEADER_* | none | One variable per header the client sends to your deployment, on header-proxy deployments. These are the exception to the rule above: rather than being baked onto the command lines, where they would show up in the process list, they are captured into an owner-only file that later launches read. See Sending additional headers. |
Read by the client itself, so it must be set in whatever starts it:
| Variable | Default | What it does |
|---|---|---|
WATCHER_MENUBAR | 1 | Set to 0 and the macOS menu bar icon never starts. No effect on Linux. |
The menu bar icon's own "Hide Menu Bar Icon" entry closes it for the current
run only; it is back at the next start. WATCHER_MENUBAR=0 is how you keep it
away.
When the client starts on login it is given almost no environment: by
default the login agent runs the binary directly with little beyond PATH.
If the running client needs environment variables (WATCHER_MENUBAR, and
also WATCHER_BACKEND_PORT and SSL_CERT_FILE where you have changed them,
since the client uses those at runtime as well as at hook-install time),
install the login agent with --launcher pointing at a wrapper script that
exports them. See
MDM rollout § Start on login.
Command reference
Running watcher with no arguments starts the client under its login agent,
enabling start-on-login on the first run. Every command supports --help.
| Command | What it does |
|---|---|
watcher | Start the client in the background under the login agent, installing it and turning start-on-login on if this is the first run. Starts in the current terminal instead while start-on-login is off. watcher live start and watcher all start remain foreground aliases. |
watcher --foreground | Start the client in the current terminal without installing or enabling the login agent. Stops a client the login agent is already running so this one can take the local port. |
watcher --version | Show the installed client version. |
watcher update | Install the client release matching the configured server. |
watcher downgrade --version <version> | Install the configured server's explicitly selected older release. |
watcher build-info | Show build details and the backend target a launch would resolve. |
watcher target set --cloud / --url <url> | Record the backend this install reports to. |
watcher hooks install | Install or repair the agent hooks for Claude Code and Codex. --yes skips the consent prompt (for unattended installs). |
watcher hooks remove | Remove the agent hooks for both agents. |
watcher hooks clear-headers | Remove the captured WATCHER_HEADER_* headers for the selected backend target (header-proxy deployments). Refuses while WATCHER_HEADER_* variables are still set in the environment: remove them first. |
watcher profile list / current / set <name> | List, show, or switch settings profiles. |
watcher autostart enable / disable | Turn starting-on-login on or off in settings. |
watcher autostart start / stop | Start or stop the login agent now, without waiting for login. |
watcher autostart enabled | Exit 0 if autostart is enabled in settings, 1 otherwise (for scripts). |
watcher autostart install [--launcher <script>] / uninstall | Install or remove the login agent itself (macOS LaunchAgent, Linux systemd user unit). By default the binary is its own launcher (client 0.16.0 and later); pass --launcher to run a wrapper script that sets environment variables at login instead. |
watcher doctor | Read-only health checks for this install (see below; client 0.14.0 and later). |
watcher doctor dump | Write a support bundle with best-effort redaction. |
watcher uninstall | Remove hooks, autostart, and the binary. |
watcher doctor
watcher doctor is the first thing to run when sessions stop appearing in
the Analyzer or blocking review stops intervening. It is read-only: it makes
no changes to the machine. The command ships with client 0.14.0 and later;
on older clients it does not exist, so if your server line predates 0.14 the
version-matched client will not have it.
What it checks
The report has four sections, and each can be run alone
(watcher doctor auth):
| Section | Checks |
|---|---|
target | A backend target is recorded, and any environment override agrees with it. |
auth | The backend is reachable, which authentication mode it runs, sign-in credentials exist and are being refreshed, and a live authenticated request succeeds. |
hooks | The installed agent hook config matches what the current client would install. |
daemon | The local services respond on their port, and whether autostart is installed and enabled. |
Reading the output
Each check reports ok, a warning, or an error, with a suggested fix in the
message. By default only problems are shown; --verbose shows passing checks
too, and --json emits the full report for scripts. The exit code summarizes
the report: 0 healthy, 1 at least one error, 2 warnings only.
watcher X.Y.Z — target: https://watcher.example.com — auth mode: workos
Auth ✗
✗ No sign-in credentials for this target. Start watcher and sign in via the
browser.
Agent Hooks ⚠
⚠ Agent hook config is out of date. Run: watcher hooks install
6 checks: 4 ok, 1 warning(s), 1 error(s)
Common failures
| Finding | What it means | Fix |
|---|---|---|
| No backend target recorded | The install never had a backend selected, so nothing can upload. | watcher target set --url https://<your-watcher-host> (or --cloud). |
| Cannot reach the backend | Wrong URL, no network route, or TLS fails against an internal CA. | Check the URL and network. For an internal CA, set SSL_CERT_FILE to your CA bundle. |
| No sign-in credentials for this target | Nobody has signed in on this machine (or the target changed, which needs a fresh sign-in). | Start watcher and sign in from the local UI. |
| Authenticated probe failed with 401 | The backend rejects this machine's requests: expired or revoked credentials, or stale auth headers overriding the sign-in. | Sign in again. If the finding names a hook-headers file, follow its instructions to remove the stale headers. |
| Credentials last refreshed N minutes ago | The running client should refresh credentials every few minutes and has stopped. | Restart watcher; if it recurs, capture a watcher doctor dump. |
| Agent hook config is out of date | The hooks in the agent config predate the current client or were edited. | watcher hooks install. |
| No backend responding on localhost | The client services are not running, so there is no UI and no sign-in refresh. | Start watcher (or watcher autostart start if the login agent is installed). |
Support bundle
watcher doctor dump writes a text bundle: the full doctor report plus
recent client logs. No session content is collected, and known secrets are
redacted, but redaction is best-effort: review the file before sharing it, as
the command's own output reminds you. --since 2h widens the log window
(default one hour) and --output sets the file path. Attach it when
reporting a client problem.