Claude Code and the Claude Agent SDK
The hook script reports what the platform saw, not what the agent said. Three hooks, one script: adapters/claude_agent_sdk/vigil_hooks.py.
| Hook | What it sends | What it does with the gate |
|---|---|---|
PreToolUse | Write, Edit, MultiEdit, NotebookEdit, and Bash commands that run cp, mv, rm, scp or rsync become file_operation (operation write, copy, move, delete or transfer). source: platform_hook, actor: agent:<session_id> | hold, alert or collapse blocks the tool: exit 2 with the reason and action id on stderr, which Claude sees |
PostToolUse | An edit whose path matches VIGIL_PROTECTED_PATHS becomes config_write with initiated_by: agent, authorized: false | Held: exit 2 so the reason reaches the model |
Stop | output_generated with content_length = the transcript file size | Never blocks; prints the reason if held |
Everything else omits authorized, so the authorization registry decides rather than the hook guessing. If the agent is not registered the hook registers it and retries once. If VIGIL is unreachable the tool runs and a warning goes to stderr; set VIGIL_FAIL_CLOSED=1 to block instead.
Install
pip install -e sdk/python # the script also finds vigil-sdk from a repo checkout without this
Environment for the Claude Code process:
VIGIL_API_URL=https://vigil.supertruth.ai
VIGIL_API_KEY=vg_... # scope events:write
VIGIL_AGENT_ID=claude-code # one registered agent per install
VIGIL_PROTECTED_PATHS=*/config/*.yaml,*/.env,*/settings.json
VIGIL_FAIL_CLOSED=0
settings.json
~/.claude/settings.json or .claude/settings.json in the project. The Claude Agent SDK takes the same shape through its hooks option; the script reads the hook JSON from stdin in both.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit|MultiEdit|NotebookEdit|Bash",
"hooks": [{ "type": "command", "command": "python /path/to/vigil/adapters/claude_agent_sdk/vigil_hooks.py", "timeout": 10 }]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit|NotebookEdit",
"hooks": [{ "type": "command", "command": "python /path/to/vigil/adapters/claude_agent_sdk/vigil_hooks.py", "timeout": 10 }]
}
],
"Stop": [
{
"hooks": [{ "type": "command", "command": "python /path/to/vigil/adapters/claude_agent_sdk/vigil_hooks.py", "timeout": 10 }]
}
]
}
}
Try it
adapters/claude_agent_sdk/example.sh pipes a sample PreToolUse (a cp of model weights), a PostToolUse on a protected path, and a Stop through the script against VIGIL_API_URL:
VIGIL_API_KEY=vg_... ./adapters/claude_agent_sdk/example.sh
Without a checkout, this is the exact event the PreToolUse hook sends for cp /models/weights.bin /mnt/backup/:
curl -sf -X POST "$VIGIL_URL/agents/" -H "Authorization: Bearer $VIGIL_KEY" -H "Content-Type: application/json" \
-d '{"agent_id":"docs-claude-code","name":"Docs Claude Code"}' > /dev/null || true
curl -sf -X POST "$VIGIL_URL/events/" -H "Authorization: Bearer $VIGIL_KEY" -H "Content-Type: application/json" \
-d '{"agent_id":"docs-claude-code","event_type":"file_operation","source":"platform_hook","actor":"agent:example-1",
"payload":{"operation":"copy","source_path":"/models/weights.bin","asset_type":"model_weights","authorized":false,"tool_name":"Bash"}}'
Send it a few times and watch the asset-movement signal fall. The hook exits 2 the moment gate_status leaves pass.