OpenClaw
The host declares this hook fail-closed itself. Its own source says an authorization hook should fail closed when a required field is absent — we did not have to impose it.
openclaw plugins install @coderifts/agent-hooks
Then confirm it loaded:
openclaw plugins inspect coderifts-contract-gate
CodeRifts contract gate
id: coderifts-contract-gate
Status: loaded
Version: 0.2.0
Two things worth knowing. The host fetches the package itself — no npm i first. And the runtime id is coderifts-contract-gate, not the package name, so inspect @coderifts/agent-hooks returns Plugin not found.
Measured on host 2026.6.35, which is the compatibility floor the package declares. The current release is not measured.
Claude Code
This host passes a stalled hook through. Its documentation says so plainly: do not count on a stalled hook to act as a gate. Our adapter therefore returns a hard refusal on every internal failure, rather than trusting the host to stop.
Two routes. The catalogue is the short one.
claude plugin marketplace add coderifts/api-governance
claude plugin install agent-hooks@coderifts
✔ Successfully installed plugin: agent-hooks@coderifts (scope: user)
Confirm with claude plugin list — it should read 0.2.0, enabled.
agent-hooks@coderifts
Version: 0.2.0
Scope: user
Status: enabled
Or wire it yourself, if you would rather pin the path:
npm i @coderifts/agent-hooks
Then one entry in your project's .claude/settings.json — the package ships it at claude-code/settings.snippet.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"timeout": 8,
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/node_modules/@coderifts/agent-hooks/claude-code/hook.mjs"
]
}
]
}
]
}
}
timeout is in seconds — the host's unit, and deliberately low: our own budget is 5000 ms, so a slow answer becomes a readable approval request rather than a host-level pass-through.
Optional environment: CODERIFTS_API_KEY, CODERIFTS_ENDPOINT, CODERIFTS_TIMEOUT_MS. Without a key the gate runs in analyse mode and every gated call ends at a human — there is no setting that makes an unanswered call pass.
What this does not see. A file written by a shell command does not go through a Write matcher. Parsing shell is not a gate — a heredoc, a tee, a python one-liner all get around it — so we do not pretend to.
Measured 2026-09-14. Marketplace install in an isolated CLAUDE_CONFIG_DIR. User ~/.claude was not written.
Docker MCP Gateway
Not built. The interceptor seam is real and we measured it on 2026-09-13: an empty stdout passes the call through, and a non-empty CallToolResult stops it before the container starts.
One finding is worth publishing even though we ship nothing here: on the HTTP interceptor path, a 403 with an empty body passes the call. The status code is not read; only a non-empty body refuses. Any integration on that path is structurally fail-open, and ours would have to live on the exec path.
Not built. Measured 2026-09-13.
Read the measurement →