Generated from
compatibility/claude-code.json,
the same record leastgrant doctor reads. Last verified 2026-09-02.
Claude Code
the real agent ran with LeastGrant installed and enforcement was observed happening.
How it attaches
A `PreToolUse` and `PostToolUse` command hook in settings.json. The payload arrives on stdin; the verdict goes to stdout as `hookSpecificOutput`.
Install it with leastgrant install. Configuration is written to
~/.claude/settings.json, alongside anything
already there — LeastGrant never removes a hook it did not add.
What a verdict does here
The three verdicts do not travel equally well. This is the table that decides whether LeastGrant is a prompt, a veto, or a suggestion on this agent.
| verdict | lands? | what actually happens |
|---|---|---|
| allow | yesprobe | hookSpecificOutput.permissionDecision |
| ask | degradesprobe | Reaches a human when a human is there. In non-interactive mode (claude -p) a hook ask becomes a DENY. The README claimed an ask reaches you in every mode; probing showed otherwise. |
| deny | yesprobe | Two channels: permissionDecision 'deny' and exit code 2. Honoured in the most permissive mode. |
Interactive and unattended
This agent tells the hook which permission mode it is in — permission_mode, optional, top-level.
Modes it reports: default, plan, acceptEdits, auto, dontAsk, bypassPermissions.
An ask reaches a person in default, plan, acceptEdits, auto, dontAsk, bypassPermissions.
Interactively. Non-interactive (-p) turns any hook ask into a deny regardless of mode.
When the hook breaks
The question nobody asks until it matters: if LeastGrant crashes, times out, or cannot start, does the tool call still run?
- On crash — the call runs anyway. (probe) Measured: hook exits 1 with non-JSON stdout, the tool ran and permission_denials was empty.
- On timeout — the call runs anyway, after 600s by default. Measured: timeout 2 against an 8s hook, the tool ran and the deny was discarded.
- Can it be made to fail closed? — no. No per-hook fail-closed option.
What it can see
A verdict is only worth as much as the set of actions it is asked about. gated means LeastGrant is consulted before the thing happens; seen after means it is told afterwards and can at best withhold the result; not covered means the action happens with LeastGrant never hearing about it.
| tool class | coverage | detail |
|---|---|---|
| shell commands | gatedprobe | |
| file reads | gatedprobe | |
| file writes | gatedprobe | |
| deletions | gatedprobe | |
| MCP calls | gatedprobe | |
| subagent spawn | gatedprobe | The model emits `Task`; the payload and matchers see `Agent`, because toolAliases resolves the name before hooks run. |
| network / web | gatedsource | WebFetch/WebSearch are ordinary tools. |
What has actually been run
Four different things, deliberately not collapsed into one badge. Reproducing an agent's invocation is not running the agent; a passing conformance suite says our side is right and nothing about whether the host ever calls us.
- Live agent test — LeastGrant installed into a real Claude Code session; PreToolUse and PostToolUse observed firing, a credential read prompted, and a completed call recorded as evidence. 2.1.240 · win32 · 2026-09-02
- Real transport probed — The hook is invoked over native stdin with the settings.json command string; exercised by every hook test in the suite driving the real binary. 2.1.240 · win32 · 2026-09-02
- Contract read from the shipped build — Hook schema, permissionDecision values and mode list read from the shipped 337 MB bin/claude.exe bundle and cross-checked against code.claude.com/docs/en/hooks. 2.1.240 · win32 · 2026-09-02
- Conformance suite — Driven by test/conformance.test.ts. 2.1.240 · win32 · 2026-09-02
Not exercised on darwin, linux.
What it cannot do
Not a disclaimer. The point of everything above is that this list exists and is specific.
- Managed policy `disableAllHooks: true` switches every hook off, including managed ones, with no signal LeastGrant can see. It is silently absent.
- Fails open on crash and on timeout, so a LeastGrant that cannot start enforces nothing.
- A hook ask in non-interactive mode is a deny, not a prompt.
- The public docs list PreModelSwitch and PostModelSwitch, which do not exist in the installed 2.1.240 binary.
- Only PreToolUse and PostToolUse are registered, so failed calls are never observed and their pending entries are never resolved.
- Verified on Windows only. macOS and Linux run the same code path but nobody has driven the real binary there.