Recipes
Claude Code hooks with CAIN
CAIN decides before Claude Code's PreToolUse hook runs anything. 3 worked recipes.
Last reviewed 2026-10-02
Claude Code runs the hook before every tool call and passes the call as JSON on stdin. A refused call is denied, a held call becomes Claude Code's own ask prompt, and anything that goes wrong inside the hook denies.
Pick the kind of tool
Shell commands
Claude code or cursor running commands: Bash
Secrets on disk
The editor touching credentials: Write
Web fetches
The agent fetching urls: WebFetch
The whole path, with shell commands as the example
1 Get a key
No key yet? Create a free account (every new account starts with a 7-day trial), then copy the key from the console. Requests go from your browser straight to this site.
Give each agent its own key in production (console → Agent keys): decisions, trust history and revocation are then per agent.
2 Install
pip install "cainstudio @ https://cainstudio.online/cainstudio-0.4.1-py3-none-any.whl#sha256=1bea5cf49aeeb2775acec58933fc39b008a1c84bfd7c9690c54723ed707af689"
export CAIN_API_KEY=...cainstudio 0.4.1, pinned by URL and SHA-256 so pip installs exactly the published wheel (the package is not on PyPI yet). Framework dependencies come from PyPI as usual.
3 Put CAIN in front of Claude Code's PreToolUse hook
Claude Code runs the hook before every tool call and passes the call as JSON on stdin. A refused call is denied, a held call becomes Claude Code's own ask prompt, and anything that goes wrong inside the hook denies.
cainstudio hook config claude-code # prints the block below
{
"hooks": {
"PreToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "cainstudio hook claude-code",
"timeout": 60
}
]
}
]
}
}
# paste it into .claude/settings.json; export CAIN_API_KEY in the shell Claude Code runs inOn a refusal the agent gets a denied tool call with CAIN's reason. The tool body never runs: not on a refusal, not on a hold, not when CAIN cannot be reached.
4 See what CAIN decides for this exact call
This is the request the integration sends when the model calls Bash with these arguments. record: false evaluates it without writing an evidence record. A new agent has no trust history, so expect REQUIRE_APPROVAL at first; destinations outside your egress allowlist are refused by the egress stage.
5 Write the rule, and test it before you save it
Piping a downloaded script into a shell is refused; your test run goes ahead.
The panel sends the draft rule to the rule tester: it says which rule would decide the call and saves nothing. Swap in the safe arguments below to see it pass through. matches is a case-insensitive glob (*, ?), not a regular expression; conditions on one rule must all hold; the first matching rule by priority wins.
Safe arguments: {"command": "npm test"}
When it does what you want, save it from the console or with POST /fabric/tool-rules:
curl -sS -X POST https://cainstudio.online/fabric/tool-rules \
-H "X-API-Key: $CAIN_API_KEY" -H 'Content-Type: application/json' \
-d '{"name": "shell-guard", "effect": "deny", "priority": 10, "match_path": "/tools/Bash", "conditions": [{"field": "command", "op": "matches", "value": "*curl *| *sh*"}]}'6 Handle the calls that wait for a person
A held call is not executed. It appears in the approval queue with its exact arguments; an approval lets that one call run once. Adapters can wait instead of returning: wait_for_approval=120.
GET /fabric/approvals
7 Check it before you ship
cainstudio redteam --agent shell-agent # dry-runs dangerous calls; exit 2 if any would run
cainstudio decisions # what was decided, and why
cainstudio explain <decision_id>Every recorded decision is signed; Cain().verify(decision) checks its quorum certificate on your machine (Python SDK).
Try CAIN-42 on your own agents
Create a free account and every new account starts with a 7-day trial of the full platform. Or try the sandbox first, with no account at all.
Create a free account → · Try the sandbox · See the whole ecosystem