Hooks in Verboo Code: how to run a command before or after any tool
Back to the blog
Articleverboo codetutorialdev toolshow to

Hooks in Verboo Code: how to run a command before or after any tool

MafraSeptember 25, 20265 min read

Verboo Code lets you plug in a shell command, or even a model-based check, to run on its own every time something specific happens in the session, with no plugin to write and no need to rely on the agent remembering to do it. That's called a hook, and the entire configuration lives in your settings.json.

What is a hook in Verboo Code?

A hook is a command the CLI fires automatically when a specific event happens, like right before using a tool, right after, or when the session starts. You configure it in settings.json, no plugin and nothing to remember to run by hand.

Verboo Code has 27 different hook events. The ones you'll actually use day to day:

EventWhen it fires
PreToolUseright before any tool runs
PostToolUseright after the tool finishes
PostToolUseFailurewhen the tool fails
UserPromptSubmitwhen you submit a message
SessionStartwhen the session opens, resumes, or clears
Stopright before the agent wraps up its response

Where do I write a hook?

In one of three files, depending on the scope you want:

  • ~/.claude/settings.json: applies to you, across every project
  • .claude/settings.json: applies only to this project, and gets versioned, so the whole team inherits it
  • .claude/settings.local.json: applies only to this project, only for you, not versioned

The structure is always the same: a hooks key, the event name, an optional matcher to filter by tool, and the list of hooks that run when it matches:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          { "type": "command", "command": "..." }
        ]
      }
    ]
  }
}

How do I make the agent format code on its own after editing a file?

This is the single most common use of a hook: running a formatter every time Verboo Code writes or edits a file, without having to ask. Add this to settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "file=$(cat | jq -r '.tool_input.file_path'); npx prettier --write \"$file\" 2>/dev/null || true"
          }
        ]
      }
    ]
  }
}

matcher gets tested as a regular expression against the tool's name, so Write|Edit catches both. The command receives the event's payload as JSON over stdin, not as an environment variable. On PostToolUse, that JSON carries tool_name, tool_input (what was passed to the tool), tool_response (what it returned), session_id, and cwd. That's why the example reads file_path with jq instead of assuming a fixed filename.

Why doesn't a hook on PostToolUse stop the tool from running? Because PostToolUse fires after the tool has already finished. There's no way to block what already happened, only react to it: warn the model, log it, trigger a fix. The one that can block the tool is PreToolUse, with a very specific exit-code behavior, verified straight from the source:

Eventexit 0exit 2any other code
PreToolUsecontinues normallycancels the call, stderr goes back to the modelcontinues, but the error is shown only to you
PostToolUsecontinues normallyshows stderr to the model, but the tool already rancontinues, error shown only to you

If you need to stop an edit from happening, the hook has to live on PreToolUse and end with exit 2.

How do I make a hook run only for a certain kind of command, like just git?

Use the if field, with the same permission-rule syntax you already use in allow and deny, for example Bash(git *). It filters before the hook process even gets spawned, so commands that don't match never run the script at all. A real example: blocking a direct push to main.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(git *)",
            "command": "cmd=$(cat | jq -r '.tool_input.command'); if echo \"$cmd\" | grep -qE 'push.*(origin|upstream)[[:space:]]+main'; then echo 'Direct push to main blocked. Open a PR instead.' >&2; exit 2; fi"
          }
        ]
      }
    ]
  }
}
Flow of a hook in Verboo Code: write the hook under PostToolUse in settings.json, the agent calls Write, the hook runs on its own receiving the JSON over stdin, and the branch for switching to PreToolUse when you need to block before it runs.
Verified in the verbeux-ai/code source on 2026-09-25, src/utils/hooks.ts and src/schemas/hooks.ts.

Does a hook always have to be a shell script?

No. There are 4 types, and each one serves a different purpose:

TypeWhat it doesUses tokens?
commandruns a shell command, bash or powershellno
httpPOSTs the event's JSON to a URLno
promptevaluates the payload with a fast model and answers ok or notyes
agentruns a full verifier agent, with whatever model you chooseyes

An agent-type hook on Stop, for example, is how you check whether tests actually ran and passed before considering a task done, instead of trusting only what the main agent claims it did.

How do I see which hooks are active in a session?

Run /hooks. It opens a browser by event, then by matcher, showing every configured hook and where it comes from: user settings, project settings, local settings, a plugin, or the session itself. It's read-only: to create or change a hook, you edit settings.json by hand, or ask Verboo Code itself to edit it for you. The old editing screen only handled command-type hooks; supporting all 4 types inside the same menu turned into too much to maintain, so the file is the official path today.

prompt and agent hooks call a model on every trigger, so on a tool that meters tokens that's a reason to use them sparingly: every PostToolUse, every Stop, every check costs something. In Verboo Code that hook draws from the same unlimited token pool as the rest of the session, so you can drop an agent verifier on every Stop without doing the math on what it costs per run.

Enjoyed this article?
Share knowledge with your network.
// Read also

Related articles