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 and updating it, where its settings live, the command
reference, and diagnosing problems with watcher doctor and watcher logs. 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. To keep one
directory's sessions off your deployment, see
Excluding a directory with .watcher-ignore.
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 remove everything (hooks, autostart, and the binary), run
watcher uninstall.
Update
Before every upgrade, read the release pages and their full changelog links for every version newer than the installed version and no newer than the target, including versions you skip. Check behavior changes and complete the steps that apply to your deployment before updating. The update guide explains how to identify the version range.
Update with:
watcher --version # what is installed now
watcher update # install the release matching the configured server
watcher --version # confirm
watcher update reads the configured server's
version and installs that release, in either direction. It keeps your sign-in,
settings, and local session data. A client running through its
login agent runs the same
reconciliation automatically every two to
two-and-a-half hours, so those machines follow the server on their own.
Before it changes anything, watcher update checks the release with GitHub. It
installs only from an immutable release, whose files can no longer be replaced,
and only when the downloaded installer matches the SHA-256 digest GitHub
records for that release. The check needs HTTPS access to api.github.com as
well as github.com. If the check cannot run or does not pass, the command says
why and leaves the installed client as it is. A client running through its
login agent tries again about an hour later. Releases 0.35.0 and earlier are
not immutable, so watcher update and watcher downgrade refuse them.
Installs that update differently:
- Clients from releases that predate
watcher updatehave no update command. Runwatcher uninstall, then re-run the installer, once; later updates usewatcher update. Sign-in and settings survive, because uninstall keeps~/.apollo_monitor/(see Per-machine settings). - Foreground-only clients (no login agent) have
watcher updatebut no automatic check: run it yourself. - Mirror-backed installs do not self-update. Publish the approved release to the mirror and redeploy through your fleet tooling (see internal mirrors).
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.
Rolling back to a release earlier than 0.36.0 isn't automatic: watcher downgrade and the login agent's check both refuse it. Reinstall the client at
the server's release on each machine instead:
watcher uninstall
curl -fsSL https://github.com/ApolloResearch/watcher-bin/releases/download/v<version>/install.sh | bash -s -- --watcher-api-url https://watcher.example.com
watcher doctor
If an update fails or leaves the client unhealthy, recovery is the same
uninstall-and-reinstall: run watcher uninstall, re-run the installer, then
check with watcher doctor. Local state under ~/.apollo_monitor/ survives
both steps.
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. Use watcher --foreground
to keep it attached to the current terminal without enabling start-on-login.
The client has no local web app. Sessions and their tool-call decisions are
reviewed in your deployment's Analyzer, and
watcher whoami prints the Analyzer link for the session it runs in (see
Command reference). Settings are the files described
under Per-machine settings. In a browser,
http://localhost:8228 (or the port set by WATCHER_BACKEND_PORT) starts
sign-in when the machine needs it. Otherwise it links to the Analyzer and
lists where each feature of the former Watcher Live web app moved. While a
browser or watcher login sign-in authenticates the machine, the page also
has a Sign out button.
Escalated tool calls
are answered in the coding agent's own permission prompt.
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): you sign in once, in a browser, through your identity provider. Run
watcherfrom a terminal on a desktop while the machine is signed out, and it waits for the client to start, then openshttp://localhost:8228/login(or the port set byWATCHER_BACKEND_PORT). That page hands off to your identity provider. On macOS, Sign in to Watcher in the menu bar opens the same page. Once you have signed in, Open Analyzer takes its place and opens your deployment's Analyzer. A start without a terminal, such as the login agent starting the client at login, never opens a browser. Recording starts once you have signed in, and the running client keeps the credentials refreshed. Sign out athttp://localhost:8228, orwatcher logout, signs the machine out.Over SSH, or on Linux without a graphical display,
watcherprintsSign in: run watcher login.instead of opening a browser. That covers a headless server or a VM you reach over SSH.watcher loginprints a URL and a code you approve from a browser on any other device, and stores the same credentials. Machines nobody signs in to use an organization API key. See Signing in without a browser. -
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.
A client that was working and later cannot authenticate says so rather than
failing quietly. On SSO deployments the macOS menu bar shows Sign in to
Watcher. In Claude Code the statusline carries a warning:
Watcher signed out: uploads paused when the machine has no usable
credentials, and Watcher auth error: uploads paused when the deployment is
rejecting the credentials it has. watcher doctor reports the same condition
in its auth check. In Codex, an approval request that arrives while the
client's hooks have stepped aside shows a warning with the request, and Codex's
own approval flow decides it. Hooks step aside when the machine has no usable
credentials, when the local Watcher service is not running, or after repeated
failures have paused grading. The warning suggests watcher login when the
client is signed out, and watcher doctor otherwise. A deployment that is
merely unreachable for one grade does not trigger this. That grade follows the
configured failure decision, described under
When grading fails.
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.
By default, these hooks are stored in user-writable configuration:
~/.claude/settings.json for Claude Code and ~/.codex/hooks.json for Codex
($CODEX_HOME/hooks.json when CODEX_HOME is set). The user, or a process
running as that user, can edit or remove them. For managed deployments, see
protecting hook configuration with an MDM.
Hooks are not tied to the client running. watcher hooks remove and
watcher uninstall remove their user-level registration. 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. Each upload also carries the machine's hostname, which labels the session in the Analyzer. Inside a container, that is the container's hostname.
The client also catches up on what no hook sent. A tool-call decision can come after a session's last upload: when a developer denies a call and the session ends there, no later hook event fires to carry that call. A background pass in the client periodically looks for sessions with decisions still waiting, uploads the part of each conversation that no hook sent, and then uploads the decisions.
The one exception is a directory an administrator has allowed you to exclude
with a .watcher-ignore file. Sessions that file covers
are not uploaded at all, so they stay on the machine and never reach the
Analyzer. Once a hook or the background pass has found a session excluded, the
background pass never uploads anything for it, even if the file is later
removed.
A conversation can also be refused by the backend. When the
Aperture gateway or another Watcher client has already recorded the same Claude Code or Codex
conversation under a different Watcher user, the backend rejects this
machine's copy (see
Two recorders). The
client then stops uploading that conversation, shows the agent a one-time
notice, and lists it in watcher doctor. Blocking review still grades and
answers its tool calls.
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.- 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 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. The client also installs a /watcher command in
Claude Code and a $watcher skill in Codex. Both run these profile commands
for you, for example /watcher profile auto.
watcher settings show prints the settings the client applies. It shows the
active profile, the enforcement mode, and whether
snooze is allowed. A
table lists each tool's flag and deny
tool thresholds
and which of them your organization locked. Below it come the settings files
the client reads, each marked present or absent. Last come local values that
do not take effect, because your organization locked them or because only the
organization can set them. Then come your rule changes: each command rule your
local files add or change, with the fields each change sets.
watcher settings show --json prints every effective setting. In that output
each flag_threshold and deny_threshold is an object with a value and a
permission of locked or modifiable. The same output lists your rule
changes under local_rules and the problems watcher doctor settings reports
under settings_problems. Problems loading a settings file, and other settings
problems, are printed on standard error, and the command still exits 0. It
works while the client is stopped.
Beyond settings, ~/.apollo_monitor/ holds the client's other local state:
a local database of sessions and pending decisions, client logs, and the
sign-in credential, auth-header and short-lived browser sign-in state 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).
Changing your own settings
To change a value your organization has not locked, edit
~/.apollo_monitor/settings.local.yaml, and create it if it does not exist.
The client reads the file again on the next tool call, so there is nothing to
restart. While a profile other than Default is active, that profile's values
take precedence over this file. To change a value under that profile, edit
settings.<profile id>.local.yaml in the same folder instead, such as
settings.auto.local.yaml for Auto or settings.permissive.local.yaml for
Human in the Loop. A file named after an alias, such as
settings.automatic.local.yaml, is ignored. watcher settings show lists the
exact path as the local_profile file.
This example asks you about more Bash commands, denies those that score 9
or higher, and approves just test without grading:
version: 1
tool_thresholds:
Bash:
flag_threshold: 4
deny_threshold: 9
command_rules:
rules:
- id: allow-just-test
tool_name: Bash
name: Allow just test
category: file_directory
regex: '^just test$'
decision: allow
priority: 300
Tool thresholds. The gateway grader scores each call from 1 to 10. A score
at or above deny_threshold denies the call, and a score at or above
flag_threshold escalates it to you. 0 means always and 11 means never.
Both at 11 allows the tool without grading, a deny threshold of 0 denies
every call, and a flag threshold of 0 with a deny threshold of 11 asks
about every call. These three also switch off the tool's command rules, deny
rules included. With other values, allow rules still approve their commands
before grading. When both
thresholds are between 1 and 10, the flag threshold must be lower than the
deny threshold. Name tools as watcher settings show lists them, such as
Bash, Edit or apply_patch.
Command rules. Give a new rule every field in the example, and a unique id.
To get the fields right, copy a built-in rule from
watcher settings show --json. regex is searched for in each part of a shell
command, ignoring case, so anchor it with ^ and $ to match a whole
command. For Edit and Write rules it is searched for in the file path.
decision is allow, triage to grade the call, human to ask you, deny,
or "off". Quote "off", because YAML reads a bare off as false. For each
part of a command, the first matching rule in priority order decides, lowest
number first. So your allow rule takes effect only when no built-in rule with
the same or a lower number also matches; check the built-in rules' numbers in the same
output. An allow rule approves a shell command only when every part of it is
allowed, so just test && ./deploy.sh still goes to grading.
Changing a built-in rule. Add an entry with the built-in rule's id and
only the fields you change, such as decision or regex.
watcher settings show --json lists every built-in rule with its id. This
example turns off the built-in rule that approves read-only file commands such
as ls. Those commands then go to grading:
version: 1
command_rules:
rules:
- id: bash-read-only-filesystem
decision: "off"
To reset your rule changes, run watcher settings reset rules. It shows what
it will change, asks you to confirm, and removes the command_rules section
from settings.local.yaml, from its profiles: entries and from each
settings.<profile id>.local.yaml. --profile <id> resets one profile only,
where default means the changes outside profiles:, which apply under every
profile. --yes skips the question. The command rewrites each file it changes,
which removes the file's comments, and first copies the file to a new folder
under ~/.apollo_monitor/settings-backups/. If your organization locked
command rules, your entries do not take effect, and watcher settings show
lists them under "Local values that do not take effect".
Never delete settings.local.yaml to start over. When that file is missing,
the client imports any settings an old client version kept in its local
database, which can bring back rules and thresholds you no longer use. Reset
the rules instead, or edit the file down to version: 1.
Checking a change. Run watcher doctor settings after each edit. It
checks every profile's settings. It reports a file that does not load, a value
outside the allowed choices, a rule missing tool_name, name, category,
regex or priority, and a change to a rule id that does not exist. It
also reports a priority that is not a whole number, an unquoted off and a
regex that does not compile. Its warnings cover a threshold outside 0 to 11,
a flag threshold that is not below the deny threshold, and a tool name that
differs from a built-in tool's only in case, spaces, - or _. A local
threshold outside 0 to 11 also causes a settings load error when its profile
is active. A broken command rule is an error when it affects the active
profile's approvals. Rule problems that apply only under another profile,
or to a disabled rule, are warnings.
An entry whose id matches no built-in or organization rule counts as a new
rule, so it needs every field unless its decision is "off". A settings file
that does not load, a rule error, or a rule missing one of those fields turns
off every approval from command rules, built-in rules included. Doctor then
reports "Command-rule approvals are off" above the problems that cause it. A
regex that does not compile turns off approvals for its rule's tool. Then
run watcher settings show: check that the table shows your tool with the
values you set, and that your rule appears under "Your rule changes".
Repairing mistakes. watcher doctor settings --fix shows the changes it
would make, asks you to confirm, and then makes exactly those changes.
Afterwards Watcher approves nothing it would not approve with the broken
entries deleted:
- A change to a rule
idthat does not exist is deleted. - A bad field in a change to an existing rule is removed, so the rule's own
value applies. If the rest of that change could let the rule approve more,
such as
decision: allowon a rule that grades, the whole change is deleted instead, and you can add back what you meant. - A new rule of your own that cannot be used is turned off with
decision: "off", and its decision is kept aslast_decision. - An unquoted
offbecomes"off"on an allow rule. On a deny, human or triage rule it is removed instead, because turning that rule off could approve commands it now stops. - An entry in
command_rules.rulesthat is not a rule, or acommand_rulessection the client ignores, is removed.
It leaves thresholds, tool names, files that do not parse, files with a
version other than 1, symlinked files and your organization's settings.yaml
as they are. A problem in a rule your organization defines is the
organization's to fix, even when your own file changes that rule. It also
leaves an entry alone when changing it would change how another profile, where
the entry works, decides, or when the change would have no effect. Like
reset rules, it rewrites each changed file without its comments after backing
it up, and --yes skips the question. A run that cannot ask, such as one from
a script, prints the changes, writes nothing and exits 1 unless you pass
--yes. When the changes uncover another problem it can repair, it says so,
and the next run shows that change before making it.
Environment variables
Almost everything about the client is configured through 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 service listens on, and the address a browser opens to sign in. 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. Captured authentication headers and a stored sign-in token take precedence over this key. 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. |
WATCHER_BACKEND_WORKERS | 2 | Number of local HTTP worker processes. Must be a positive integer. Set to 1 to reduce memory use. A second worker can answer new requests while one worker is busy; background synchronization still runs once. Restart the client after changing this value. |
WATCHER_BACKEND_RELOAD | 0 | Source development only: set to 1 for automatic reload when source files change. Reload mode uses one request worker and overrides WATCHER_BACKEND_WORKERS. No effect on the packaged client. |
On macOS, the menu's "Watcher found problems" item opens the doctor report.
It appears while Watcher is signed in and an agent session runs with
approvals bypassed (see Common failures). It also
appears when a settings file fails to load or the last organization settings
sync left a warning that watcher doctor settings reports as one, even while
signed out. The client checks about every 30
seconds, so the item can take that long to appear, or to clear after a fix.
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, WATCHER_BACKEND_WORKERS, 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. Exporting a variable only in an interactive shell does not change
the login agent's environment. 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. On an SSO deployment, a run from a desktop terminal while signed out also opens the browser sign-in page once the client is up. 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 whoami [<session ID>] | Print the Analyzer link for a Claude Code session or Codex thread: the one the command runs in, or the ID you pass. Exits non-zero with a one-line reason when that session is not in Watcher. |
watcher login / watcher auth login | Sign in from the terminal without a local browser: prints a URL and a code to approve from a browser on any other device (SSO deployments; needs a server that offers device sign-in). See Signing in without a browser. |
watcher logout / watcher auth logout | Sign out: asks the deployment to end the stored sign-in's session, then deletes the stored credentials for the selected target. API keys and captured authentication headers stay. See Signing out. |
watcher auth helper set / show / check / refresh / clear | Configure and diagnose a trusted CLI that supplies proxy bearer tokens. See External credential helpers. |
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 settings show [--json] | Show the effective settings, which values your organization locked, the settings files read and your rule changes (see Per-machine settings). |
watcher settings reset rules [--profile <id>] [--yes] | Remove your command-rule changes from your local settings files after confirmation, backing each file up first. Never deletes settings.local.yaml (see Changing your own settings). |
watcher rules test <tool> <input> [--json] | Show which command rule or tool threshold decides a tool call on this machine, without running the call. Pass the command for Bash, the file path for Edit and Write, or the tool's input as a JSON object for other tools. See Command rule examples. |
watcher decisions list [--session <ID>] [--limit <N>] [--json] | List the 20 most recent tool-call decisions on this machine, newest first, with each call's input, the decision, the reason and its recorded outcome or human answer. --session shows only one Claude Code session or Codex thread, and --limit sets how many to show. The list includes decisions that never upload, such as those from sessions a .watcher-ignore file excludes. |
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; pass --launcher to run a wrapper script that sets environment variables at login instead. |
watcher doctor | Health checks for this install (see below). They never create Watcher's local database. Read-only, except watcher doctor settings --fix. |
watcher doctor settings --fix [--yes] | Repair the settings problems doctor can repair safely, after showing the changes and asking you to confirm (see Changing your own settings). |
watcher doctor dump | Write a support bundle with best-effort redaction. |
watcher logs [hooks|daemon|backend] | Show client logs, merged across sources by default (see below). -n sets lines, -f follows, --since 2h sets a window, --errors filters. |
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 does not change configuration, except that watcher doctor settings --fix repairs settings problems after you confirm. On a deployment using an
external credential helper, its authentication checks can
invoke that helper and update the token cache.
What it checks
The report has eight 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 | Backend reachability, authentication mode, the selected credential, sign-in refresh status, and a live authenticated request where supported. API-key authentication on SSO deployments skips that live check. |
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. |
enforcement | No open agent session runs with an approval policy that bypasses blocking review (for example a Codex session launched with --dangerously-bypass-approvals-and-sandbox). Such a session is still recorded, but Watcher cannot grade or block its tool calls before they run. |
uploads | No conversation was refused because a different Watcher user already records it. |
settings | The active settings profile, the enforcement mode and the snooze state. Run from a directory, it also reports whether a .watcher-ignore file excludes that directory. A settings file or value that fails to load is an error, and a warning from the last organization settings sync is a warning. A failed sync becomes a warning only after three failed attempts in a row, about ten minutes, and organization settings this client version does not understand are listed in the section without a warning. Sync warnings expire 15 minutes after the last completed sync attempt, whether that attempt succeeded or failed. Continuing failed attempts keep the warning current. It also checks every profile's command rules and thresholds for mistakes the client would otherwise apply without a word, such as a rule missing a field. This section is shown even when it passes. |
decisions | How long the oldest tool-call decision waiting to upload to the deployment has waited, whether any decisions are waiting on a session that never uploaded, and whether any decisions ran out of upload retries. Decisions more than seven days old are left out, as are sessions a .watcher-ignore file excludes and sessions the deployment refused for another Watcher user. The report titles this section Decision delivery. |
Reading the output
Each check reports ok, a warning, or an error, with a suggested fix in the
message or on a Fix: line under it. By default only problems and the
Settings section are shown, with the other passing sections named on one line;
--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. Run `watcher login`, or on macOS use
Sign in in the menu bar.
Agent Hooks
⚠ Agent hook config is out of date. Run: watcher hooks install
Settings
Active profile: Default (default)
Enforcement mode: enforce
Snooze: off (not allowed by your organization)
.watcher-ignore: not honored on this machine.
✓ Target, Daemon, Enforcement, Uploads, Decision delivery
1 error, 1 warning, 9 ok
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. |
| Cannot read captured hook headers / duplicate authentication fields | The saved headers file is malformed or contains the same authentication header more than once with different casing. Doctor skips backend authentication probes. Support dumps omit log excerpts when credential or header files cannot be read or parsed for redaction. | Repair the file named in the finding, or remove the WATCHER_HEADER_* exports and run watcher hooks clear-headers. |
| No sign-in credentials for this target | Nobody has signed in on this machine (or the target changed, which needs a fresh sign-in). When the finding adds that the hooks are disabled, nothing else on the machine can authenticate either: sessions are not recorded or graded, permission questions are not answered, and the coding agent's own permission prompt is all that gates its tool calls. | Run watcher login, or on macOS use Sign in in the menu bar. See Signing in without a browser for remote machines. An unattended install can set WATCHER_API_KEY instead. |
| Authenticated probe failed with 401 | The backend rejects the selected credential: expired or revoked credentials, or stale auth headers overriding the sign-in. | Follow the finding for the selected authentication method: run watcher login for sign-in credentials, or correct the configured authentication headers and proxy setup. Signing in does not replace captured authentication 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 the hooks cannot reach it and no sign-in refresh runs. | Start watcher (or watcher autostart start if the login agent is installed). watcher logs daemon shows why the last start failed. |
| The login agent is loaded but has never been started | macOS only. launchd loaded the login agent but deferred its first launch, so the client will not start on its own. Doctor reports this even when another Watcher instance answers on the port. | watcher autostart start. |
| Conversation is recorded under a different Watcher user; this machine's copy is not stored | The Aperture gateway or another client recorded this Claude Code or Codex conversation first, under another Watcher user. Usually your Aperture account and your Watcher sign-in are different users. The agent showed the same notice once, and this machine no longer uploads the conversation. Blocking review still works. | Run watcher login to sign Watcher in as the same user as your Aperture account. On a proxy-authenticated deployment, which identifies you by headers instead, make the identity headers your gateway or your WATCHER_HEADER_* settings send name that user, then run watcher hooks install. Either step lets refused conversations upload again. |
| Session runs with approval_policy "never" | The agent was launched with approvals bypassed (or resumed a session that was), so it never asks for approval and Watcher cannot intervene: it can only record. While such a session is open and Watcher is signed in, the macOS menu bar icon shows a warning glyph (⚠) unless a sign-in or auth-error warning takes priority, and its menu gains a "Watcher found problems" item that opens this doctor report. Clicking the "Not enforcing" notification opens the session in the Analyzer once it has uploaded, and this doctor report otherwise. | Remove the bypass flag from however the agent is launched and start a fresh agent session; resumed sessions keep their original policy. |
| Command-rule approvals are off | A settings mistake listed below this finding stops every command rule from approving a tool call, so even commands such as ls go to grading. Under another profile it is a warning, which takes effect when you switch to that profile. | Fix each problem listed, or run watcher doctor settings --fix to repair the ones it can. To start over, run watcher settings reset rules. For the organization's settings.yaml, ask an administrator. |
| Settings load error naming a file and setting | The client could not load that settings file or value. It ignores what it could not load and uses the other settings layers instead. | Fix the file and setting the finding names, then run watcher doctor settings. For the organization's settings.yaml, ask an administrator to fix the managed client settings or the MDM profile. |
| Organization settings sync, followed by a message | The client could not fetch your organization's managed client settings for about ten minutes, could not read them, or applied only the parts this client version understands. The last case is listed in the Settings section without a warning. The message can also say that the enforcement mode changed but Codex still has its old saved policy. If the client could not fetch or read your organization's settings, it keeps the ones it already had. If it applied only part of them, each setting it ignored falls back to the built-in default, or to your local value when your organization did not lock that setting. | Do what the message says. For the Codex policy, restart Watcher, and restart Codex before its next session. For settings this version does not understand, update the client. The client pulls the settings again every few minutes while it runs. |
| A tool-call decision has been waiting to upload | A decision this machine made has waited more than 30 minutes and has not reached the Analyzer. A short wait is normal while a session is active or an upload is retried. | Read watcher logs hooks for delivery errors, and run the full watcher doctor to check sign-in and the local services. |
| Tool-call decisions are waiting on sessions that never uploaded | A decision has waited more than 30 minutes because its session's transcript has not reached the deployment. A session's decisions upload only after its transcript does, so a rejected or repeatedly failing transcript upload holds them back. | Read watcher logs hooks for transcript upload errors, and run the full watcher doctor to check sign-in and the local services. |
| Tool-call decisions from the last seven days exhausted their upload retries | The client stopped retrying these decisions, so their current results will not reach the Analyzer. The warning includes the last upload error. | Fix the cause the error names, using watcher logs hooks for detail. Fixing it does not resend these decisions; a later change to a decision can queue it again. |
Support bundle
watcher doctor dump writes a text bundle: the full doctor report plus
recent client logs. With an external credential helper, authentication is a local
snapshot: the dump does not invoke the helper or probe upstream authentication. 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.
watcher logs
watcher logs prints the client's own logs, so
diagnosing a problem does not require knowing where the log files live. It
covers three sources:
| Source | What it holds |
|---|---|
hooks | The agent hooks: one line per tool call evaluated, plus upload and sync errors. |
daemon | Output captured from the client the login agent runs, including why a background start failed. |
backend | The local client services' own log. |
watcher logs # last 50 lines from all sources, merged
watcher logs hooks -n 200 # one source, more history
watcher logs -f # keep streaming new lines (Ctrl-C to stop)
watcher logs --since 2h --errors # warnings and errors from the last 2 hours
With no source argument, lines from all sources are merged in time order and
prefixed with their source name. --level sets a minimum level
(debug|info|warning|error|critical); --errors is shorthand for
--level warning. The logs are read from files on disk, so the command works
while the client is not running; the daemon source is the place to look when
the client will not start. When output is piped rather than shown in a
terminal, lines pass through as they appear in the log files. For JSON
processing use watcher logs backend, the one source that holds only JSON
records: the daemon source is captured process output that can mix JSON
with plain text, the hook log is plain text, and the merged view prefixes
every line with its source.
watcher logs output is not redacted. To share diagnostics outside the
machine, prefer watcher doctor dump above, which redacts known secrets.