Package Exports
- cc-hooks-ts
Readme
cc-hooks-ts
Define Claude Code hooks with full type safety using TypeScript.
See examples for more usage examples.
[!NOTE] Starting with versions 2.0.42, we will raise our version number to match Claude Code whenever Hook-related changes occur.
This ensures we can adopt newer type definitions while maintaining compatibility.
Installation
# npm
npm i cc-hooks-ts
# yarn
yarn add cc-hooks-ts
# pnpm
pnpm add cc-hooks-ts
# Bun
bun add cc-hooks-ts
# Deno
deno add npm:cc-hooks-tsBasic Usage
Define a Hook
import { defineHook } from "cc-hooks-ts";
const hook = defineHook({
// Specify the event(s) that trigger this hook.
trigger: {
SessionStart: true,
},
// Implement what you want to do.
run: (context) => {
// Do something great here
return context.success({
messageForUser: "Welcome to your coding session!",
});
},
});
// import.meta.main is available in Node.js 24.2+ and Bun and Deno
if (import.meta.main) {
const { runHook } = await import("cc-hooks-ts");
await runHook(hook);
}Configure Claude Code
Then, load defined hooks in your Claude Code settings at ~/.claude/settings.json.
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bun run -i --silent path/to/your/sessionHook.ts"
}
]
}
]
}
}If you are using native install, you can run hooks with Bun via BUN_BE_BUN=1 claude.
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "BUN_BE_BUN=1 claude run -i --silent path/to/your/sessionHook.ts"
}
]
}
]
}
}Tool Specific Hooks
In PreToolUse, PostToolUse, and PostToolUseFailure events, you can define hooks specific to tools by specifying tool names in the trigger configuration.
For example, you can create a hook that only runs before the Read tool is used:
const preReadHook = defineHook({
trigger: { PreToolUse: { Read: true } },
run: (context) => {
// context.input.tool_input is typed as { file_path: string; limit?: number; offset?: number; }
const { file_path } = context.input.tool_input;
if (file_path.includes(".env")) {
return context.blockingError("Cannot read environment files");
}
return context.success();
},
});
if (import.meta.main) {
const { runHook } = await import("cc-hooks-ts");
await runHook(preReadHook);
}Then configure it in Claude Code settings:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read",
"hooks": [
{
"type": "command",
"command": "bun run -i --silent path/to/your/preReadHook.ts"
}
]
}
]
}
}Custom Tool Types Support
You can add support for custom tools by extending the tool type definitions.
This is useful when you want to your MCP-defined tools to have type-safe hook inputs.
import { defineHook } from "cc-hooks-ts";
// Example: type-safe hooks for DeepWiki MCP Server tools
declare module "cc-hooks-ts" {
interface ToolSchema {
mcp__deepwiki__ask_question: {
input: {
question: string;
repoName: string;
};
response: unknown;
};
}
}
const deepWikiHook = defineHook({
trigger: { PreToolUse: { mcp__deepwiki__ask_question: true } },
run: (context) => {
// context.input.tool_input is typed as { question: string; repoName: string; }
const { question, repoName } = context.input.tool_input;
if (question.length > 500) {
return context.blockingError("Question is too long");
}
return context.success();
},
});Advanced Usage
Conditional Hook Execution
You can conditionally execute hooks based on runtime logic using the shouldRun function.
If shouldRun returns false, the hook will be skipped.
import { defineHook } from "cc-hooks-ts";
const hook = defineHook({
trigger: {
Notification: true,
},
// Only run this hook on macOS
shouldRun: () => process.platform === "darwin",
run: (context) => {
// Some macOS-specific logic like sending a notification using AppleScript
return context.success();
},
});Advanced JSON Output
Use context.json() to return structured JSON output with advanced control over hook behavior.
For detailed information about available JSON fields and their behavior, see the official documentation.
Async JSON Output (Experimental)
[!WARNING] This behavior is undocumented by Anthropic and may change.
[!CAUTION] You must enable verbose output if you want to see async hook outputs like
systemMessageorhookSpecificOutput.additionalContext.You can enable it in Claude Code by going to
/configand setting "verbose" to true.
Async JSON output allows hooks to perform longer computations without blocking the Claude Code TUI.
You can use context.defer() to respond Claude Code immediately while performing longer computations in the background.
You should complete the async operation within a reasonable time (e.g. 15 seconds).
import { defineHook } from "cc-hooks-ts";
const hook = defineHook({
trigger: { PostToolUse: { Read: true } },
run: (context) =>
context.defer(
async () => {
// Simulate long-running computation
await new Promise((resolve) => setTimeout(resolve, 2000));
return {
event: "PostToolUse",
output: {
systemMessage: "Read tool used successfully after async processing!",
},
};
},
{
timeoutMs: 5000, // Optional timeout for the async operation.
},
),
});Documentation
For more detailed information about Claude Code hooks, visit the official documentation.
Development
# Run tests
pnpm test
# Build
pnpm build
# Lint
pnpm lint
# Format
pnpm format
# Type check
pnpm typecheckHow to follow the upstream changes
Install the latest version of
@anthropic-ai/claude-agent-sdkand runpnpm run check.- If the command passes without errors, there are no type changes.
Get diff of the types. This example gets the diff between Claude Code 2.0.69 and 2.0.70:
npm diff --diff=@anthropic-ai/claude-agent-sdk@0.1.69 --diff=@anthropic-ai/claude-agent-sdk@0.1.70 '**/*.d.ts' # Only for humans, You can use dandavison/delta for better diff visualization npm diff --diff=@anthropic-ai/claude-agent-sdk@0.1.69 --diff=@anthropic-ai/claude-agent-sdk@0.1.70 '**/*.d.ts' | delta --side-by-side
Reflect the changes.
- Edit
src/hooks/for changed hook input / output types.- No need for adding tests in most cases since we are testing the whole type definitions in these files:
src/hooks/input/schemas.test-d.tssrc/hooks/output/index.test-d.tssrc/hooks/event.test-d.tssrc/hooks/permission.test-d.ts
- No need for adding tests in most cases since we are testing the whole type definitions in these files:
- Edit
src/index.tsfor changed tool input / output types.
- Edit
License
MIT
Contributing
We welcome contributions! Feel free to open issues or submit pull requests.
Made with ❤️ for hackers using Claude Code