# Troubleshooting

Measured problems and fixes, checking hooks with chock doctor, reading the decision log, install notes per agent, what happens when a guard cannot decide, and arming a fresh clone.

This page starts with problems a person measured, and their fixes. Then it shows how to check that each agent's hooks are in place and running, and how to read what they decided. Last come each agent's install notes as the engine prints them, and the engine docs on the cases that most often surprise people.

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

## Measured problems and fixes

### Windows: a Claude Code marketplace update fails with an SSL certificate error

Measured on 2026-10-12, on Windows with antivirus that intercepts HTTPS: Claude Code's marketplace update failed with `SSL certificate problem: unable to get local issuer certificate`, while a plain `git fetch` in the same shell succeeded. What fixed it: `git config --global http.sslBackend schannel`, then cloning the stale marketplace copy again. The chock plugin for Claude Code was not affected: it installs from a local folder marketplace, with no git fetch.

## Check the hooks: chock doctor

In most clients, if a hook crashes, times out or never loads, the action goes through anyway. So a silent hook looks exactly like a protected session. Ask the user before running a command this note names: `chock doctor` checks that each client's chock hooks are in place, current, and seen running, both in repo hooks and in the plugin. It only reads. It uses no network and never runs a client. Each check prints one line, `PASS`, `WARN`, `FAIL` or `INFO`, with a one-line fix:

- Hooks: the file the client reads runs chock's runtime, and the runtime is there.
- Launcher: the launcher is there, and a `python3`, `python` or `py` on PATH is 3.11+.
- Engine: the runtime matches, byte for byte, what the installed chock builds. For a plugin, doctor also compares the engine version and commit the plugin recorded.
- Trust: some clients (Codex, Grok) run a hook only after a person trusts it. Doctor shows how to do that. Chock cannot read the client's record of it.
- Client: the client versions agentseam has seen working in a live run. `--client-version` compares yours with them.
- Last fired and heartbeat: from the decision log, the last record per hook point, and the `session_start` heartbeats. A client with no documented session-start event reads "no heartbeat available", never a failure.

It exits 1 when any check fails or it finds no chock hooks, else 0.

### `chock doctor`

Check each client's hooks on each route: present, current, and seen running (--client X)

Ask the user before running the commands below:

```text
usage: chock doctor [-h] [--repo REPO] [--client CLIENT] [--client-version CLIENT_VERSION]
                    [--sessions SESSIONS] [--json]
```

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

[The engine's doctor docs](https://github.com/open-coder-ai/chock/blob/5a4b6287a46e07f9f856d9bbe10622bd1ff2e021/docs/doctor.md)

## Read what the hooks decided

The decision log (see Monitor) is where to look when a policy did not fire, or fired when you did not expect it. What the engine docs say about reading it:

- Ask the user before running a command this note names: Run `chock doctor` first. In most clients, if a hook crashes or never loads, the action goes through anyway. So a session with no `session_start` heartbeat is the sign of a hook that did not run, and doctor reports it.
- Ask the user before running a command this note names: `chock status --only log` lists the decisions from both install methods. At the default level, `info`, chock writes only blocks, asks, warns, guard faults and overrides. It does not write allows.
- To see every call, allows included, with a redacted trace (wire event, tool, matched rule, the guard's exit code, per-stage timings), a person sets the level to `debug`: `CHOCK_LOG_LEVEL=debug` in the shell the agent client was started from, or `{"level": "debug"}` in `.chock/chock-log.json`. An agent cannot change the level.
- Ask the user before running a command this note names: `chock status --summary` lists the top guard faults and warns when a hook's p95 is within 25% of its client's hook timeout, because a call that runs past that timeout goes through.
- Logging never changes a verdict, never raises and never blocks. If chock cannot write a line, it drops the line silently.
- Ask the user before running a command this note names: `chock witness export --client <client>` writes that client's records, redacted, to a new local file and prints the agentseam issue to attach it to. It uploads nothing.

## Install notes per agent

### Claude Code (claude-code)

Both commands are safe to repeat. The plugin loads in place from the directory: restart the Claude Code session, or run /reload-plugins in it, to pick up a rebuild.

### Cursor (cursor)

Cursor loads ~/.cursor/plugins/local/chock-guardrails as a local plugin: run "Developer: Reload Window" (or restart Cursor), then check it under Customize. There is no command to run.

### Codex (codex)

Then install chock-guardrails from the chock-local marketplace in /plugins, and review and trust its hooks in /hooks: until you do, they do not run.

### GitHub Copilot (copilot)

VS Code finds plugins the Copilot CLI installed. Without the CLI, add the plugin folder to your VS Code user settings yourself:

### Devin (devin)

On Windows, `devin plugins install --local` creates a symbolic link, so it needs Developer Mode (Settings > System > For developers) or an elevated shell; otherwise it fails with "os error 1314: A required privilege is not held by the client".

The plugin is linked in place: a rebuild applies from the next Devin session.

### Grok (grok)

Plugins are off in grok until enabled, and chock does not edit grok's config: run `grok plugin list` for the name grok gave this plugin (chock expects chock-guardrails, its folder name; the docs do not say how a plugin with no plugin.json is named), then `grok plugin enable <name>`, or add it to `enabled` under [plugins] in ~/.grok/config.toml, or press Space on it in the Plugins tab. Re-run the install command after a rebuild.

### goose (goose)

goose discovers ~/.agents/plugins/chock-guardrails as a user plugin, in every project: start a new goose session. There is no command to run (`goose plugin install` takes a git URL only). To turn it off, add chock-guardrails to disabledPlugins in ~/.config/goose/settings.json.

### Antigravity (antigravity)

Antigravity 2.0 and the IDE read plugin folders from ~/.gemini/config/plugins/ instead: copy ~/.chock/marketplace/antigravity/chock-guardrails there. Its hooks name ~/.chock/marketplace/antigravity/chock-guardrails by absolute path (Antigravity documents no plugin-root variable), so keep that folder where it is; re-run the install command after a rebuild.

### OpenHands (openhands)

OpenHands loads a local plugin when it starts: run `openhands --plugin ~/.chock/marketplace/openhands/chock-guardrails`, or add "~/.chock/marketplace/openhands/chock-guardrails" to sources under [plugins] in ~/.openhands/config.toml (chock does not edit it). Its hooks name ~/.chock/marketplace/openhands/chock-guardrails by absolute path (OpenHands documents no plugin-root variable), so keep that folder where it is; a rebuild applies from the next start.

### Kimi Code (kimi-code)

Kimi Code installs plugins from its TUI only, and chock does not drive it: in Kimi Code run `/plugins install ~/.chock/marketplace/kimi-code/chock-guardrails`, then `/reload` (or `/new`). Kimi copies the plugin to $KIMI_CODE_HOME/plugins/managed/chock-guardrails/ and runs that copy, so after a rebuild install it again. The plugin applies to every project you open (Kimi has no project-level plugin scope); `/plugins disable chock-guardrails` turns it off.

### Factory Droid (factory-droid)

Run these from the root of the project the plugin is for: `--scope project` records it in that project's Droid settings. The marketplace is local to this machine; chock publishes nothing.

Droid names a local marketplace from its source; if `droid plugin marketplace list` shows a name other than chock-local (a --dest folder named otherwise), install chock-guardrails@<that name>. Droid copies the plugin into its cache: after a rebuild run `droid plugin marketplace update` and `droid plugin update chock-guardrails@chock-local --scope project`, then start a new session.

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

## What happens when the guard cannot decide

The levels above grade the **outer** boundary: what the client does when chock's hook never
runs or dies outright. There is an inner one too — what the hook says when it *did* run and
could not reach a verdict — and the two answers are not the same. `gate/guard_runner.py`
distinguishes six such causes and answers only one of them with an allow.

| Cause | What chock returns | Why |
| :--- | :--- | :--- |
| The command will not tokenize (unbalanced quotes, trailing backslash, PowerShell quoting) | the guard's own verdict | The guard still runs: argv is a whitespace split (`CHOCK_ARGV_FALLBACK=1`) and `CHOCK_RAW_COMMAND` carries the exact string. `rm -rf / #'` is valid bash and invalid shlex, so a guard that reads the raw command must get the chance to refuse it. |
| The command is empty after tokenizing | allow | There is nothing to check. |
| No bash on the machine can resolve the guard | **ask** | No guard ran. The prompt names the fix: install Git for Windows (it ships bash), or put bash on PATH. |
| The guard the hook names is not on disk | **deny** | The hook config names it, so its absence is a broken install, not nothing to check. The reason says to run `chock sync --repo .`. |
| The guard crashed, exited a code that is none of 0, 1 or 3, or exited 1 with no reason or a traceback / syntax error | **ask** | The control was installed, reachable and runnable, and still produced no answer. (Exit 3 is not this: it is the guard asking on purpose, and its own first line is the prompt. Exit 1 blocks only with a reason on stderr, as `spec/script-backed-gates.md` states.) |
| The guard hit its 30-second timeout | **ask** | Same: the control ran and did not decide. |

A guard that fails refuses or asks; it never reports an allow it never established.

**The engine's time limits always fire before the client's.** A client's hook timeout fails **open** (the command runs); the engine's own limits ask a person or deny. So chock keeps one budget, `ENGINE_BUDGET_SECONDS` (30 s, `gate/budget.py`), per hook invocation: the bash probes, `git status`, the guard, the gate runner and a tool-call script all draw on the same deadline, and each subprocess gets only what is left. A `git status` that does not finish at the turn's end is a refusal, never a clean worktree. The timeout written into the client's hook config is that budget plus a 15 s interpreter start-up margin (`TIMEOUT_SECONDS`, 45 s), in each client's own unit.

| Surface | Clients | Timeout written |
| :--- | :--- | :--- |
| In-agent (`chock sync`: `.claude/`, `.cursor/`, `.codex/`, `.github/hooks/`, ...) | Claude Code, Codex, Copilot, Cursor, Devin, Antigravity, Gemini CLI, Grok, Tabnine | yes, every hook entry |
| In-agent | Windsurf | **no key**: its documented hook entry takes `command`, `powershell`, `show_output` and `working_directory` only, so its own default (not documented) applies |
| Plugin package (`hooks.json`) | Claude Code, Codex, Copilot, Cursor, Devin, goose, Grok, Antigravity, OpenHands, Kimi Code, Factory Droid | yes, every hook entry |

| Client | Field | Unit | Written | Client default when the key is absent |
| :--- | :--- | :--- | :--- | :--- |
| Claude Code, Codex, Devin, Antigravity, Grok, Kimi Code (in `kimi.plugin.json`), Factory Droid | `timeout` | seconds | 45 | Codex 600 s; Devin 600 s (command hooks); Antigravity 30 s; Grok 5 s; Kimi Code 30 s; Factory Droid 60 s |
| Gemini CLI, Tabnine | `timeout` | milliseconds | 45000 | 60000 (60 s) both |
| Copilot | `timeout` and `timeoutSec` | seconds | 45 | not checked |
| Cursor | `timeout` (with `failClosed`) | seconds | 45 | not checked |
| Windsurf | none | n/a | n/a | undocumented |

A default below 45 s is exactly the hole this closes: Grok's 5 s and Antigravity's and Kimi Code's 30 s would end the hook before the engine's own 30 s limit and the command would run unchecked. `tests/test_hook_timeout_budget.py` walks every emitted hooks document and fails on a hook entry that lacks a timeout above the budget, unless its client is in the no-key allow-list (`NO_TIMEOUT_KEY_VENDORS`). Sources are in `src/chock/data/hook_timeouts.json`.

**What an `ask` becomes depends on the client, and no client turns it into a silent allow.**

| Client | Gate chock installs | An `ask` on the wire |
| :--- | :--- | :--- |
| Claude Code | PreToolUse, via the settings fragment `install-hooks` merges | `permissionDecision: "ask"` — prompts the user to confirm |
| VS Code agent mode / Copilot CLI | PreToolUse, via the agent-hooks file | `permissionDecision: "ask"` — forces a confirmation, and overrides the client's own auto-approve |
| Cursor | `beforeShellExecution`, via the Cursor hooks file | `{"permission": "ask"}` — honoured at this gate. Cursor's generic `preToolUse` accepts the value but does not enforce it, which is one reason chock installs the shell gate instead |
| Codex CLI (hand-wired only) | PreToolUse | **deny.** Codex's own parser rejects `ask` as unsupported and then fails open on the response it rejected, so agentseam's adapter degrades it to a deny rather than emit a value that would silently permit the call. A guard broken on every command therefore blocks on Codex where the others prompt |

Each row is cited to vendor source or vendor documentation at a named ref, so a reader can
recheck it rather than take this table's word:

- **Claude Code** — `code.claude.com/docs/en/hooks`, read 2026-08-31, `PreToolUse`
  `hookSpecificOutput` field table: *"`"ask"` prompts the user to confirm."* Claude Code is
  not open source, so this is documentation, not source.
- **VS Code agent mode** — `microsoft/vscode` at
  `718038e170df9c66a15087cebda424d9c7f051ff`,
  `src/vs/workbench/contrib/chat/browser/tools/languageModelToolsService.ts`.
  `resolveAutoConfirmFromHook` (`:877-930`) synthesises a confirmation and sets
  `allowAutoConfirm: false`; `:622-627` carries the comment *"A preToolUse hook that returned
  `ask` explicitly forces a confirmation, so never let `preApproved` override it."*
- **Cursor** — **not verified from a vendor artifact.** The claim rests on agentseam's
  recorded doc-basis verification (2026-08-26), which distinguishes `preToolUse` (accepts
  `ask`, does not enforce it) from `beforeShellExecution` (honours it). Second-hand, and
  labelled as such rather than presented as checked.
- **Codex CLI** — `openai/codex` at `32f48598a0609a882e5847f0d3e35d6d67f375bc`.
  `codex-rs/hooks/src/engine/output_parser.rs:458-460` returns *"PreToolUse hook returned
  unsupported permissionDecision:ask"*; `:144` computes a block reason only when nothing was
  rejected, and `codex-rs/hooks/src/events/pre_tool_use.rs:234-244` sets `should_block` in the
  non-rejected arm alone — so a literal `ask` there would let the call through.

**This raises no coverage grade.** A control is only as strong as its worst degradation, and
the empty command above still allows — so chock's in-agent controls stay at the level the
ladder gives a control that degrades to allowing until that grade is re-derived deliberately.

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

## How to verify a fresh clone enforces

After `git clone` in a fresh directory, the committed artifacts are present but the git hooks
are not — `.git/hooks/` is never cloned, so the repo enforces **nothing** until the hooks are
reinstalled:

1. Run `chock sync --repo .` to install the hooks from the committed artifacts.
2. `ls .chock/bin/gate.py` should exist.
3. `ls .chock/compiled/scan-secrets/git-hook/gate.json` should exist.
4. Commit a file containing a credential. `scan-secrets` should block it and print the policy message.

`chock init` and `chock sync` write these un-ignore lines to your `.gitignore`, so a global ignore
rule cannot keep the gate out of your commits. The `bin/` in the Visual Studio, .NET and Java
templates is the usual culprit: it matches `.chock/bin/`, and every agent hook then names a file a
clone does not have. `chock check` reports a hook target that is missing or git-ignored. If your
own `.gitignore` has a broad `.chock/` rule, keep the lines below after it:

Ask the user before running the commands below:

```gitignore
!.chock/bin/
!.chock/bin/**
!.chock/compiled/
!.chock/compiled/**
```


---

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).
