Model Control with Hooks

Hooks are the enforcement layer of Xedant Code’s validation system. While skills guide the model’s behavior through instructions, hooks enforce rules by blocking specific tool calls before they execute. The most impactful use of hooks is preventing the model from running build and lint commands manually — since automated builds handle this more efficiently, every manual build the model attempts is wasted time and tokens.

Hooks dialog

Hook Events and Types

Hooks are organized by event type. Xedant Code supports two events:

  • PreToolUse — Runs before a tool is executed. This is where deny rules and pre-execution commands are defined. Hooks can target specific tools: Bash, Edit, Write, Read, Glob, Grep, Agent, NotebookEdit, and others
  • UserPromptSubmit — Runs when the user submits a prompt to the AI. Use this to validate or modify user input before processing

Deny Hooks

Deny hooks block tool execution by matching patterns against the tool’s input. For Bash tools, the pattern is matched against the command string. For non-Bash tools, use .* as a catch-all pattern to deny the tool entirely.

Each deny hook has four fields:

  • Pattern — The text or regex pattern to match against the tool input
  • Regex — When enabled, the pattern is treated as a regular expression (case-insensitive). When disabled, it’s matched as a substring
  • Message — The denial message shown to the user when the hook triggers

Execute Hooks

Execute hooks run a shell command before the tool executes. They receive context about the tool call via stdin as JSON and can return structured output that influences the model’s behavior. The $CLAUDE_PROJECT_DIR environment variable is available in the command.


Preventing Manual Builds

This is the single most impactful use of hooks in Xedant Code. When automated builds are configured, the model should never attempt to build, lint, or start the project manually. Without these hooks, the model routinely runs build and lint commands after every edit — wasting time and tokens on actions the build system already handles.

preToolUse:
  Bash:
    - type: deny
      pattern: "dotnet build"
      message: "Don't build projects manually - it's done automatically"
    - type: deny
      pattern: "npm run build"
      message: "Don't build projects manually - it's done automatically"
    - type: deny
      pattern: "npm run link"
      message: "Don't lint projects manually - it's done automatically"
    - type: deny
      pattern: "npx svelte-check"
      message: "Don't lint projects manually - it's done automatically"
    - type: deny
      pattern: "npm run check"
      message: "Don't lint projects manually - it's done automatically"
    - type: deny
      pattern: "npm run dev"
      message: "Don't start projects manually - it's done automatically"
    - type: deny
      pattern: "dotnet run"
      message: "Don't start projects manually - it's done automatically"
userPromptSubmit: {}

When a deny hook blocks a command, the model receives the denial message and adjusts its behavior. Over the course of a session, this saves dozens of unnecessary tool calls that would compound into significant time and cost savings. You must always start with adjusting skills and CLAUDE.md to minimize amount of model-initiated builds, using hooks as a last-resort guardrail when the model forgets about your requirements when the chat gets long or compacted.


Claude Code Integration

Hook configurations stored in .xedant/hooks.yml are automatically synced to Claude Code’s native hooks system. The sync process converts YAML definitions into JSON format and writes them to .claude/settings.local.json, so the hooks take effect on the next user prompt in the chat.

The sync flow works as follows:

  1. On startup and after each save, HooksService reads the YAML configuration
  2. Each hook definition is converted to Claude Code’s JSON format with the executable path resolved automatically
  3. Hooks are deduplicated by command to avoid conflicts
  4. The JSON is written to .claude/settings.local.json
  5. Currently running sessions use the old hooks until the next user prompt is sent

The executable path for hook processing is resolved automatically by checking several locations: next to the running process binary, in the application base directory, or falling back to dotnet XedantCode.dll.


Other Validation Uses

Beyond preventing manual builds, hooks serve other validation purposes:

  • Blocking destructive operations — Deny rm -rf, force pushes, or other dangerous commands that could cause irreversible damage even inside a container
  • Enforcing project-specific policies — Block writes to specific protected files, prevent edits to configuration files, or deny modifications to test fixtures
  • Custom validation logic — Execute hooks can run shell commands before tool execution, enabling pre-commit checks, format validation, or custom guards

Config validation automatically checks hooks.yml on save, warning about unknown event types and properties. Errors reference the relevant section in .xedant/README.md.


Effective Validation Patterns

  • Always deny manual builds when automated builds are configured — This is the highest-impact hook configuration. Add deny rules for every build and lint command the model might try
  • Use substring matching for simple patternspattern: "npm run build" with regex: false catches any command containing that substring, including with extra flags
  • Use regex for complex patterns — Enable regex: true when you need case-insensitive matching or pattern syntax like rm\s+-rf
  • Test hooks after saving — Hooks take effect on the next user prompt. Send a test prompt and verify the hook blocks the expected commands

For the complete hooks reference, see Hooks. For the build system hooks prevent manual runs of, see Build & Deploy. For Docker isolation that makes hooks a safety net rather than a gate, see Docker Isolation & Skip Permissions.