Claude Agent SDK (TypeScript)
Add Checkrd enforcement to @anthropic-ai/claude-agent-sdk via PreToolUse / PostToolUse hooks.
Anthropic Claude Agent SDK — TypeScript
The Claude Agent SDK exposes hooks on ClaudeAgentOptions — async functions invoked at well-defined points in the agent's run loop. Checkrd ships factory functions for the four most common events; user-supplied hooks coexist on the same matchers.
Install
npm install checkrd @anthropic-ai/claude-agent-sdkQuickstart
import { query, type ClaudeAgentOptions } from "@anthropic-ai/claude-agent-sdk";
import { initAsync } from "checkrd";
import { attachToOptions } from "checkrd/claude-agent-sdk";
const checkrd = await initAsync({
policy: "policy.yaml",
agentId: "claude-agent",
});
const options: ClaudeAgentOptions = {};
attachToOptions(options, {
engine: checkrd.engine,
agentId: "claude-agent",
sink: checkrd.sink,
enforce: true,
});
for await (const msg of query({ prompt: "summarize this", options })) {
console.log(msg);
}attachToOptions adds Checkrd hooks for PreToolUse, PostToolUse, UserPromptSubmit, and Stop. Idempotent — calling it twice does not register duplicates. User-supplied hooks remain in place.
Manual wiring
If you only want one of the events, use the factory functions directly:
import {
makePreToolUseHook,
makePostToolUseHook,
} from "checkrd/claude-agent-sdk";
const pre = makePreToolUseHook({
engine: checkrd.engine,
agentId: "claude-agent",
enforce: true,
});
const post = makePostToolUseHook({
engine: checkrd.engine,
agentId: "claude-agent",
});
const options: ClaudeAgentOptions = {
hooks: {
PreToolUse: [{ matcher: "Bash|Write|Edit", hooks: [pre], timeout: 30 }],
PostToolUse: [{ hooks: [post] }],
},
};The matcher is a regex over tool names; leave it undefined to match every tool.
What gets enforced
| Hook event | Synthetic URL |
|---|---|
PreToolUse | https://claude-agent.local/tools/{tool_name} |
UserPromptSubmit | https://claude-agent.local/prompts/user-prompt |
agent: claude-agent
default: allow
rules:
- name: deny-destructive-bash
deny:
url: "claude-agent.local/tools/Bash"
body:
command: "*rm -rf*|*dd if=*"
- name: deny-secret-write
deny:
url: "claude-agent.local/tools/Write"
body:
file_path: "*.env|*.pem|*secrets*"Deny semantics
The hook returns { decision: "block", systemMessage: "<reason>" } per the SDK's documented protocol. The claude-code subprocess interprets this as "do not run the tool" and reports the message back to the agent — the model sees the rejection in its conversation history and can adapt.
Observation mode
attachToOptions(options, { engine, agentId, sink, enforce: false });The hook returns {} (no block) on deny but emits the deny telemetry event so dashboards show what would have been blocked.
Caveats
- All hooks are
async. The SDK rejects sync hooks at registration. - Hook latency adds to every tool call. The WASM engine's
evaluate()is sub-ms, but the hook IPC to theclaude-codesubprocess adds a few ms. Keep hooks fast. - Duck-typed against the SDK shape — the
HookMatcherandClaudeAgentOptionstypes in the adapter are structural so a minor SDK bump doesn't force a Checkrd release.