Welcome to the final chapter of this tutorial series!
In the previous chapter, AI Verification & Interaction, we gave our system a "brain" by allowing it to ask an AI for help. We can now run commands and consult LLMs.
However, we still have a problem with precision.
Let's say you configured a hook to run a "Linter" every time a file is written.
WriteFile.npm run lint.
This works great when you save a JavaScript file. But what happens when you save a Markdown (.md) file? Or a plain text (.txt) file? The system still tries to run the linter. It fails, throws errors, and wastes resources.
The matcher we learned in Chapter 1 is like a sledgehammerβit hits everything. We need a scalpel. We need to say:
"Only run this hook IF the file ends in
.ts."
if FieldTo solve this, we introduce the Conditional Execution Logic.
This is a special field called if that lives inside your hook definition. It acts like a second security guard. Even if the Event matches, this guard checks your ID card to make sure you belong there.
tool_finish).*.ts)?"Let's configure a rule that only runs a test command when a TypeScript file is saved.
{
"matcher": "WriteFile",
"hooks": [
{
"type": "command",
"command": "npm test",
// The Logic Gate
"if": "WriteFile(*.ts)"
}
]
}
Explanation:
matcher: "WriteFile": The system wakes up whenever the WriteFile tool is used.if: "WriteFile(*.ts)": Before running npm test, the system looks at the arguments passed to WriteFile.script.ts, the pattern matches *.ts. RUN.README.md, the pattern fails. SKIP.
The text inside the if field uses a specific syntax known as Permission Rules.
Format: ToolName(Pattern)
| Rule Example | Meaning |
|---|---|
WriteFile(*.json) |
Only runs when writing JSON files. |
Bash(git commit *) |
Only runs when the command starts with git commit. |
Bash(npm *) |
Only runs for NPM commands. |
How does the system decide whether to block or proceed?
if string in the hook definition.
Let's look at how this is defined in hooks.ts.
First, we define what the if field looks like. It is just a string, but the description helps documentation generators understand its purpose.
// hooks.ts
const IfConditionSchema = lazySchema(() =>
z.string()
.optional()
.describe(
'Permission rule syntax (e.g., "Bash(git *)"). ' +
'Only runs if the tool call matches the pattern.'
),
)
We add this schema to every type of hook (Command, Prompt, Agent, HTTP). This means any action can be conditional.
// hooks.ts (inside BashCommandHookSchema)
const BashCommandHookSchema = z.object({
type: z.literal('command'),
command: z.string(),
// Here is our logic gate
if: IfConditionSchema(),
shell: z.enum(SHELL_TYPES).optional(),
});
Why this matters:
By adding IfConditionSchema() to the object, Zod allows the user to add the "if" key in their JSON settings. If the user omits it (it is .optional()), the system assumes the condition is "Always True."
Let's combine everything we've learned in this tutorial.
Goal: When I run a git commit, I want an AI to verify the commit message is professional, but I don't want this to happen for git status or git add.
{
"matcher": "Bash",
"hooks": [
{
"type": "prompt",
"prompt": "Is this commit message professional? $ARGUMENTS",
// Only runs for commits!
"if": "Bash(git commit *)"
}
]
}
Bash tools.git commit commands using if.prompt hook to ask the AI for advice.Congratulations! You have completed the Schemas project tutorial.
We have built a powerful, reactive system from scratch:
You now possess the knowledge to define complex, intelligent, and safe automation workflows using Zod schemas!
Generated by Code IQ