Skip to main content
Working with an agent? Give them a link to this page as markdown.

Blocking and trailing review

Watcher reviews a running session in two ways. Blocking review evaluates tool calls before they run and can stop them. Trailing review grades the session's trajectory as it grows and surfaces findings after the fact. The dividing line is simple: if a single action could cause irreversible harm, it belongs to blocking review. Patterns that emerge across turns belong to trailing review.

Blocking review runs from the Watcher client on the developer's machine. You configure it through the client's settings: which graders run, the thresholds, and the enforcement mode. These resolve on each machine, but you do not have to set them machine by machine. Admins can distribute and lock them across the organization with managed settings. Gateways can use the same evaluation pipeline through gateway hooks; the caller then applies the returned decisions. Trailing review runs on the Watcher server over ingested sessions and needs no client configuration.

Blocking review​

Blocking review evaluates the tool calls a coding agent proposes before they run. Coverage depends on the agent. With Claude Code, every tool call is evaluated. With Codex, review runs through the agent's approval flow, so a call Codex does not ask about executes without a pre-execution decision. That call is still recorded, and graded after the fact. Codex asks about the commands it considers dangerous, and about anything that needs to reach outside its sandbox. That is why Watcher keeps the sandbox on when it is enforcing.

Subagent tool calls can also reach blocking review through the coding agent's hooks. See Subagent coverage for an example and the limits on the context available to that review.

The agent is paused while a call is evaluated, so the pipeline is staged from fastest to slowest. Most calls never reach the slow stages:

  1. Terminal thresholds. A tool whose thresholds are set to a terminal value is decided instantly: always-allow (both thresholds disabled), always-deny, or always-flag. These outrank everything that follows, including command rules. Setting a tool to always-allow also switches off that tool's command rules, deny rules included.
  2. Command rules. Match rules that allow, deny, require grading, or send a call straight to human review.
  3. Triage. A fast, cheap grader scores the call. Triage can only auto-approve: a low-scoring call proceeds immediately, and anything else falls through to gateway review. It never denies or escalates.
  4. Gateway review. The gateway grader scores the call from 1 to 10. The score is compared against the tool's configured thresholds: at or above the deny threshold the call is denied, at or above the flag threshold it is escalated, otherwise it is allowed.
  5. Human escalation. An escalated call goes to the coding agent's own permission prompt, and the developer answers it there. A Claude Code run started with claude -p cannot show a prompt, so Watcher denies the call at once. That includes a run that answers prompts through --permission-prompt-tool. Its reason suggests the Auto settings profile, unless the call would escalate under Auto too. When the call escalated because grading failed, the reason suggests checking Watcher's connection and sign-in with watcher doctor auth, and signing in again with watcher login. Escalations from Claude Code subagents, including background ones, also appear in the main session's prompt. Once such a call runs, Watcher records the approval as the developer's decision on the developer's machine. The Analyzer does not show it. Watcher does not record a refusal, because subagent calls are not in the session's transcript.

By default, every built-in settings profile allows AskUserQuestion and ExitPlanMode outright, so they are never escalated. The agent still asks its question, or asks to approve its plan, in its own dialog.

Command rule examples​

Default command rules can approve routine shell commands without an LLM grade. Examples include rg --files | sort -u | uniq -c, gh pr checks 123, and gh run view 456 --log-failed.

These are examples, not a complete list. Approval depends on the command's flags and pipeline stages. For example, head or tail in these pipelines needs a numeric line count and no filename. Quoted text that looks like one of these pipelines can also trigger review. To check which rules apply on a machine, run watcher settings show --json there and read its settings.command_rules, or review your organization's managed settings. To see which rule decides one call, run watcher rules test on that machine, for example watcher rules test Bash 'git push --force'. It prints the matching rule and its action, or says that the call is sent to grading. The result does not account for the enforcement mode, a snooze or a .watcher-ignore file, which still apply to the real call. A command rule's allow never approves a call that Codex asks permission for: Watcher grades that call instead. A tool threshold set to always allow still approves it.

Variable expansions and command substitutions need grading because they can change what runs after a rule has matched the text. Directory-stack commands and shell dialects that the rules cannot parse also need grading. Output redirects are checked against file-write rules, including quoted paths and forced overwrites.

Allow rules approve shell commands only for the Bash tool. Watcher treats other tools as shells too when they have a shell name, such as shell or exec_command, or when their input has a command or cmd field. That includes MCP tools. Edit, Write and apply_patch are never treated as shells. Deny and human-review rules still apply to these other shell calls, but allow rules cannot approve them, so they go to grading.

Valid deny and human-review rules still apply if another active rule is invalid. Watcher withholds rule-based approvals until the invalid rule is fixed. This prevents a broken restriction from leaving a broader approval rule in force. When managed settings are saved, each active rule must have complete fields and a valid regex of at most 16,384 characters. This includes rules in profiles.

If rule matching times out or a shell command is longer than 131,072 characters, the call goes to grading on both the client and the gateway. In enforce mode, when the client skips grading, such as while snoozed, it asks for human review instead. A deny or human-review match already found still applies. Terminal thresholds take precedence.

The default Bash policy sends suspicious commands to review. It does not deny them automatically. Admins can configure deny rules and thresholds through managed settings.

When grading fails​

Every outcome for a call in the session's transcript is recorded as a decision on the session, so its escalations and denials are visible in the Analyzer transcript afterward. Calls from Claude Code subagents are not in that transcript, so the Analyzer does not show their decisions. See Subagent coverage.

If grading fails or exceeds its time budget, Watcher applies the configured failure decision: escalate, deny, or allow. Escalate is the default: a human decides in the coding agent's own permission prompt.

Watcher retries some failures before applying the decision, and applies it right away for others. The line is whether the backend answered:

  • Retried once (by default). A transient failure where the backend answered but could not grade: it returned a server error or was rate limited, an upstream model provider failed, or the grade came back empty. A short pause, then one more attempt.
  • Applied immediately. A refusal that another attempt cannot change: an authentication error, a grader model your deployment does not serve, or a usage limit reached. A transcript too long for the grading model is not a failure: the server removes the middle of what the monitor sees and marks the cut.
  • Not retried: a grade that runs out of time. One attempt may use the whole time budget, so there is nothing left to retry with. And a grader slow enough to exhaust the budget would only be slow again.
  • Not retried: a backend that does not answer at all. The connection is checked before grading starts, so this is the one case above that never reaches a grade to retry. A refused or unanswered connection applies the failure decision within a couple of seconds, rather than holding the call for the full time budget.

The client sends each call it cannot decide locally to the Watcher server's gateway hook endpoint, which runs triage and gateway review, or answers with a judgment it already recorded for the same call. A retry sends the call again, triage included. The time budget is for a server that answers slowly, where a gateway review may legitimately need most of it. When triage itself fails, the server still runs gateway review, since triage can only approve a call. When triage runs past its own short time limit, the server starts gateway review and lets triage keep running: if triage then approves the call before gateway review answers, the server approves it and stops the gateway review.

When failures repeat​

Repeated failures pause a stage rather than the client. Failures while the deployment is answering are counted against gateway review. If gateway review is failing, the client asks for triage alone, triage keeps auto-approving the calls it can, and only the rest take the failure decision.

The exception is a failure decision of allow. With gateway review paused, every call would be allowed whichever way triage scored it. So calls are allowed immediately and not graded at all until gateway review resumes.

Failures that show the deployment itself is unreachable are counted against the deployment instead. Enough of those pause grading on the machine for a minute, and each gated call goes to the coding agent's own permission prompt.

Every pause lifts by itself.

A machine with no usable credentials, such as a signed-out one, does not grade at all. Its hooks step aside, and the coding agent's own permission flow decides each call. See the Watcher client for how the client reports this.

There is one exception to the configured decision. On an SSO-mode deployment, a machine that has never signed in, or has signed out, can still grade when it holds an API key: one set in WATCHER_API_KEY, or one captured from the coding agent's own requests. If the deployment rejects that grade as unauthenticated, the call is blocked with a prompt to sign in, not with the configured decision. The block covers that call only. Grading continues, and later calls are blocked the same way while the rejections continue. After five rejections in a row the client stops sending grades, apart from an occasional probe, until one succeeds. Calls then follow the configured decision, or go to the agent's own permission prompt when the key was captured. The remedy depends on the key. A key set in WATCHER_API_KEY is superseded by signing in. A captured key is used ahead of a stored sign-in, so it must be replaced or removed first. watcher doctor shows which credential is selected and whether grading is paused, but it does not validate an API key against an SSO deployment. The first accepted grade is what confirms recovery. Any other failure kind on such a machine still follows the configured decision above.

Denial limits​

An agent that keeps being denied is stuck in a loop that more denials will not resolve. So two limits turn a would-be gateway denial into a human escalation instead:

  • After two consecutive gateway denials, the next one escalates.
  • After ten gateway denials since the developer last interacted (a new prompt, or answering an escalated call in the coding agent's permission prompt), the next one escalates. This applies even when allows were interspersed; only a human interaction resets that count. A refusal Claude Code makes by itself does not reset it. Neither does a refusal in a subagent's prompt, which Watcher cannot match to its call.

Denials from terminal thresholds and command rules never count toward either limit, and always stand. Both limits apply in enforce mode only, including under the Auto profile. So in a claude -p run, Watcher denies a denial-limit escalation without suggesting the Auto profile. The limits are tunable in the client settings' admin-only enforcement block: escalate_after_consecutive_denies (default 2) and escalate_after_accumulated_denies (default 10).

Enforcement modes​

The client's enforcement mode controls what blocking review does with its judgments:

  • Enforce. Decisions take effect: denials block the call, escalations go to the coding agent's own permission prompt.
  • Observe. The pipeline still grades calls and records what it would have decided, but never intervenes. With Codex, observe-mode grading happens after each call has run. Observe mode is useful for tuning thresholds before turning enforcement on.
  • Paused. Watcher decides nothing: grading stops, and neither command rules nor tool thresholds run, so even an always-deny threshold or a deny rule does not block. Every call falls through to the coding agent's own permission flow, and the client still records the call. Paused works the same way for gateway hooks, which only ingest the transcript.

A .watcher-ignore file skips LLM grading for the sessions it covers, and keeps them from being uploaded. Unlike paused mode, it never turns off the deterministic checks: command rules and tool thresholds still decide those sessions' calls.

Snoozing​

Snooze temporarily skips LLM grading without changing the enforcement mode. In enforce mode, command-rule denials and requests for human review still apply. Calls without a rule decision use the coding agent's own permission flow. Observe and paused modes keep the behavior described above.

On macOS, choose Snooze grading in the Watcher menu on the menu bar, pick 1, 4 or 8 hours, and enter a reason. Resume grading ends a snooze early. The menu offers snoozing only when your organization's snooze.enabled setting allows it. Linux has no snooze control.

Trailing review​

Trailing review runs asynchronously on the Watcher server, on a periodic schedule over recently active sessions. Nothing on the developer's machine triggers or configures it. Two built-in jobs run today:

  • Summaries. The built-in Summarize grader produces a title and short summary for each session: an early first summary while the session is still active, an updated one once it goes quiet, and periodic refreshes for sessions that never go quiet.
  • Deep Review. Sessions the blocking pipeline scored highly get a second, more thorough security review once they settle.

A session without a deep review shows a Generate report button in the Analyzer to its owner and to users with sessions:grade:any (which admins have by role). Content access is not required. It queues a deep review of the session's latest transcript, and the report appears when the review finishes, which can take several minutes. Asking again for an unchanged transcript returns the same review. A queued or running review shows as Report queued or Grading in progress to everyone who opens the session. When a session's report predates its latest activity, the same users see an Update report button beside it; while the newer review is queued or running the header shows Updated report queued or Updating report, and the old report stays until the new one replaces it. If a review fails, the same users can Retry it, a few times per transcript. A requested review that overlaps a scheduled deep review of the same session waits for it and reuses its report. To request and poll a review from your own tooling, use the Python SDK or HTTP API.

Trailing review never blocks the agent and never feeds anything back into the agent's context. Its grades appear in the Analyzer, alongside grades from any other grader. Sessions whose scores reach your organization's flag threshold are flagged for review, and can trigger a Slack notification (see organization settings).

What drives the schedule when you self-host​

A xylon-grading-scheduler container triggers trailing review every five minutes by default. It asks the API to grade each organization's recent sessions. The API performs the grading. The scheduler reads no session content, holds no provider keys, and contacts no provider. It runs the xylon-api image with a different entrypoint, so it is not a separate image to deploy or review.

The scheduler runs trailing review on its periodic schedule. Reports requested with Generate report are produced by the xylon-operation-worker container instead, which the operation-worker profile starts and which requires PostgreSQL 17 or later; without it, those requests stay queued. The async-grading Compose profile starts the scheduler, and .env.example enables both profiles for new installs. The Kubernetes Helm chart runs the scheduler but not the worker, so requested reports stay queued there too. Without the scheduler, a deployment still records sessions, and clients or gateway hooks can evaluate individual tool calls, but trailing review does not run. See Update Watcher for update instructions.

Choosing what goes where​

Blocking reviewTrailing review
Evaluatesone tool call, before it runsthe trajectory so far
Can interrupt the agentyesno
Latency budgetseconds; the agent is pausedgenerous; runs behind the session
Suited toirreversible single actions: destructive commands, secret exposure, dangerous infrastructure changescross-turn patterns: wrong diagnoses, premature completion claims, ignored instructions, scope creep
Results appear inthe Watcher client (live) and the Analyzer (as decisions)the Analyzer (as grades)