# Monitor

Where chock hooks log their decisions for each install method, how to read and sum up the log, and how to export it as evidence.

Every chock hook writes one line per decision to a local log. Nothing leaves the machine. \`chock status\` reads the log and sums it up. \`chock witness export\` writes one client's records to a file you attach yourself.

> Ask the user before running any command on this page: each one changes how their agent or repository behaves.

## The decision log

Every chock hook writes one line per decision to a local JSONL file. It uses no network and sends no telemetry, and it never logs a file's contents. Each install method has its own file, and the engine keeps one rotated file beside it:

- Repo hooks, in the repository: `.chock/log/gate-events.jsonl`, `.chock/log/gate-events.1.jsonl`
- Plugin, in your home folder, one folder per agent: `~/.chock/log/<agent>/events.jsonl`, `~/.chock/log/<agent>/events.1.jsonl`

Read it with chock status. Its options, as the engine prints them:

### `chock status`

Policy states and coverage (--only registry,log for more)

Ask the user before running the commands below:

```text
usage: chock status [-h] [--repo REPO] [--only ONLY] [--summary] [--verify-log]
                    [--head SEQ:SHA256] [--policy POLICY] [--since DAYS] [--json]
                    [--by {policy,rule,agent,event}] [--format {text,md}] [--route {sync,plugin}]
                    [--level {info,debug}] [--verdict {allow,block,ask,warn,off}]
                    [--plugin PLUGIN] [--follow]
```

The full options are on this page's HTML version, as the engine prints them.

### `chock gate-log`

Alias of: status --only log

Ask the user before running the commands below:

```text
usage: chock status --only log [-h] [--repo REPO] [--policy POLICY] [--since DAYS]
                               [--route {sync,plugin}] [--level {info,debug}]
                               [--verdict {allow,block,ask,warn,off}] [--plugin PLUGIN] [--follow]
                               [--json] [--by {policy,rule,agent,event}] [--format {text,md}]
```

The full options are on this page's HTML version, as the engine prints them.

## Sum it up: chock status --summary

`chock status --summary` reads the same log and reports:

Ask the user before running the commands below:

    chock status --summary [--since DAYS] [--route sync|plugin] [--json]

- Fires per policy and per client, with block, ask, warn and fault counts and rates. A fault is a hook that could not check the call: reason `guard-errored`, `guard-missing` or `fault`.
- The p50 and p95 `duration_ms` per hook (client and hook point). At `info`, chock logs only the calls a policy fired on, so the latencies cover those. Set `debug` to time every call.
- A WARN when a hook's p95 is within 25% of its client's hook timeout. When a hook runs past the client's timeout, the call goes through, so a call slower than that goes through unchecked. It shows an undocumented timeout as `unknown` and never warns on it.
- The top 5 guard faults, by policy and reason code.
- Heartbeats (`session_start` records): hook records with no `session_start` before them, and `session_start` records with no hook record after them, per install method and client. For a client with no session-start event in agentseam's records, the summary says "no heartbeat available", never that one is missing.

## Export evidence: chock witness export

`chock witness export --client <client>` writes one client's records from both install methods, redacted, to a new local JSON file, and prints the agentseam issue to attach it to. It refuses to overwrite an existing file. It uploads nothing: you read the file and attach it yourself.

### `chock witness export`

Export one client's redacted decision log for its agentseam evidence issue (witness export --client C)

Ask the user before running the commands below:

```text
usage: chock witness export [-h] --client CLIENT [--since DAYS] [--out OUT] [--repo REPO]
```

The full options are on this page's HTML version, as the engine prints them.

## The settings are a person's

Files under `.chock/` are a person's to change, the log settings `.chock/chock-log.json` and `~/.chock/chock-log.json` included. Every chock bundle refuses an agent's write to either settings file, through a shell command (best effort) or an Edit/Write. If a repository installs protect-agent-config (0.7.6 in the pinned catalog), the policy refuses any agent write to the `.chock/` folder, by shell or by Edit/Write. Its gate makes one exception. It leaves `.chock/state` and `.chock/log` to the engine, which owns them. Reading stays allowed. A client with no chock bundle, in a repository without that policy, has no such refusal. Edit the files yourself, in your own editor or shell.

[The policy's page](https://chock.sh/docs/policies/protect-agent-config/index.md)

From the engine docs: [docs/decision-log.md](https://github.com/open-coder-ai/chock/blob/5a4b6287a46e07f9f856d9bbe10622bd1ff2e021/docs/decision-log.md)

## Levels

| Level | Writes |
| :--- | :--- |
| `off` | nothing |
| `info` (default) | one line each time a policy fires: block, ask, warn, a guard fault, or an override or rollout level that let a finding through; and one line per allowed agent tool call, carrying only its `target` fingerprint. However many policy hooks judge that call, its allow is one line, written at once by the first hook to run and naming no policy (no `policy_id`, `plugin` or match fields): an allowed call is the call's, not any one policy's. A call is known by the client's `tool_use_id`, else by session, second and `target`, so with no id two identical calls in one second are one line. Every block, ask, warn, fault and override keeps its own line per policy |
| `debug` | every hook call (one allow line per policy hook), the turn's end included, plus a `trace`: wire event, tool, matched rule (or "no rule matched"), the guard's exit code, per-stage timings, and the command or path cut to 200 characters with every value the scan-secrets policy would block replaced by `[redacted]` |

`chock status` counts allowed tool calls once each, in total (`allowed_calls`), and never toward a
policy: its per-policy columns show what a policy decided, its blocks, asks, warns and overrides.

Each line carries `ts`, `route`, `level`, `agent`, `client` (when the payload names one), `event`,
`policy_id`, `surface`, `verdict`, `reason`, `duration_ms`, `can_ask`, `actor`, `session_id` (the
client's session id, when the payload carries one) and, on a tool call, `target`. The sync route adds
`chock_version` (left out when the hook's Python has no chock installed). The plugin route adds
`plugin` and `plugin_version` instead, never `chock_version`: a plugin runs its own bundled Python, not chock. On the sync route a compiled gate's line is written by `gate.py`
and keeps its own fields (`kind`, `match_count`, `matches`, `rules`), plus `actor`, and `session_id`
and `target` from the hook that ran it. A git hook's line carries `actor` only: a commit is no tool call.

## Heartbeat

Where the client documents a session-start event, chock wires one more hook there (the runtime,
with no flags) on both routes, and it writes one line per session at `info`:

Ask the user before running the commands below:

```json
{"ts": "...", "event": "session_start", "route": "sync", "agent": "codex_cli", "session_id": "...", "chock_version": "0.15.2", "level": "info"}
```

The plugin route's line carries `plugin` and `plugin_version` instead of `chock_version`. The
hook never refuses and never blocks the session; a heartbeat that cannot be written is dropped.
Most clients fail open when a hook crashes or never loads, so a session with no heartbeat is the
sign of a hook that did not run: `chock doctor` reports it. Antigravity, Mistral Vibe and
Windsurf document no session-start event, so they have no heartbeat.

## Settings (a person's, only)

Ask the user before running the commands below:

```json
{"level": "debug", "max_bytes": 4194304}
```

One file per scope, both optional, each named `chock-log.json` inside a `.chock` folder:

1. the environment: `CHOCK_LOG_LEVEL=off|info|debug` in the shell the agent client was started from;
2. the repository: `<repo>/.chock/chock-log.json`;
3. the user: `~/.chock/chock-log.json`;
4. the default: `info`, `max_bytes` 1048576 (1 MiB).

The first that sets a value wins. `max_bytes` must be a whole number from 65536 (64 KiB) to
1073741824 (1 GiB); a value outside that range, an unknown level or key, or unreadable JSON is
ignored and named by `chock status --only log`. `CHOCK_GATE_LOG=0` still works: it is a deprecated
alias for `CHOCK_LOG_LEVEL=off`.

An agent cannot change the level. A hook's environment comes from the client, not from the
command an agent writes, so `CHOCK_LOG_LEVEL=off rm -rf build` changes nothing. Every chock bundle
refuses an agent's write to either settings file, raising or lowering, through a shell command (best
effort) or an Edit/Write (`chock-guardrails-protect`). A client with no chock bundle installed has
no such refusal: there the settings files are protected only by whatever policies the repository
itself installs. Edit the file yourself, in your own editor or shell.

Logging never changes a verdict, never raises and never blocks: a line that cannot be written is
dropped silently.

## Reading it

Ask the user before running the commands below:

```bash
chock status --only log                       # both routes, per policy
chock status --only log --route plugin --plugin <name> --verdict block --since 7
chock status --only log --level debug --json  # allows included
chock status --only log --follow              # print new lines as they are written
```


---

Built from engine [5a4b6287a46e](https://github.com/open-coder-ai/chock/commit/5a4b6287a46e07f9f856d9bbe10622bd1ff2e021) and catalog [1e60d211c1bb](https://github.com/open-coder-ai/chock-catalog/commit/1e60d211c1bb767152c6ab0167932da06ab3a10f).
