PolarSPARC

Claude Code Adventures - Part 5


Bhaskar S 04/03/2026


Overview

In Part 4 of this series, we covered the topic on Claude Skills, which are reusable instruction(s) that extend the capability of Claude for a specific domain, such that Claude can automatically handle task(s) or workflow(s) in that specifc domain, in a repeatable way.

In this part, we will cover the topic on Claude Hooks !!!

Hooks in Claude are user-defined commands (event handlers) that are automatically executed at specific points in Claude code's lifecycle. They provide deterministic control over Claude's behavior, ensuring certain actions always happen rather than relying on the LLM to choose to run them.

Hooks in Claude can be used in the following scenarios (not exhaustive):

When an event fires and there is a corresponding hook registered to handle that event, Claude Code will pass a JSON context about the event to the registered hook handler command. Note that the JSON input will arrive via stdin to the command handler. For HTTP based hook handler, the JSON input will arrive in the POST request body. The hook handler can then inspect the JSON input and make decisions to take the appropriate action. Note that some events will fire once per session, while others fire repeatedly inside the agentic loop.


Hands-on with Claude


To create a hook, one needs to add a hooks block in the configuration settings file that is defined as a JSON file called settings.json.

The settings file located at $HOME/.claude/settings.json applies to all the projects for the user, while the settings file located within a specific project at .claude/settings.json applies just to that project.

If the command that handles a specifc hook trigger is located at $HOME/.claude/hooks/, then it applies to all the projects for the user. On the other hand, if the command that handles a specifc hook trigger is located in a specific project folder ./.claude/hooks/, it applies just to that specific project.

For the demonstration, we will create a project Project-4 specific hook(s) that will be located at $HOME/Claude/Project-4/.claude/hooks/.

At the Claude prompt, let us make a request to create the new project folder, create an empty settings.json file, and make that folder the working directory by executing the following request:


> mkdir -p Project-4/.claude/hooks/ && echo "{}" > Project-4/.claude/settings.json && cd Project-4


The request will cause Claude Code to seek our permission and once we confirm Yes, Claude Code will proceed with our request and respond as shown in the illustration below:


Claude Project 4 Setup
Figure.1

One of the hooks is the UserPromptSubmit event, which is triggered when the user submits a prompt, before Claude processes it.

The JSON input for a UserPromptSubmit hook event will have the following fields (in addition to others):

We will implement a hook command for this event using bash, which will log the prompt for audit purposes at /tmp/claude-prompts.log.

Let us go ahead and create the hook command as a bash script in the folder .claude/hooks.

The following is the bash script to process the user prompt before Claude processes it:


log-prompt.sh
#!/usr/bin/env bash

# stdin: JSON with .session_id and .prompt
DATE=`date`
SESSION_PROMPT=`jq -r '"\(.session_id) :: \(.prompt)"'`

echo "${DATE} -> ${SESSION_PROMPT}" >> /tmp/claude-prompts.log

We need to register the just created hook command as the handler for the UserPromptSubmit hook event in the settings.json file. The following should be the modified contents of the settings.json file as shown below:


.claude/settings.json
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/log-prompt.sh"
          }
        ]
      }
    ]
  }
}

In the end, the folder structure along with the files for our first hook for UserPromptSubmit is as shown in the illustration below:


Hooks Folder Structure
Figure.2

At the Claude prompt, type the following command and press the ENTER key:


> /hooks


The Claude menu options appear as shown in the illustration below:


Claude Hook Options
Figure.3

Notice the highlighted menu option - it is our first hook for UserPromptSubmit !!!

Press the ESC key to exit our custom skill actions menu options.

At the Claude prompt, type the following prompt and press the ENTER key:


> what hooks are enabled for this project


Claude Code will proceed to ask the user for permission as shown in the illustration below:


Claude User Permission
Figure.4

Open a separate terminal window and execute the following shell command:


$ cat /tmp/claude-prompts.log


The following would be a typical output:


Output

Sun Mar 29 07:23:48 PM EDT 2026 -> 8aa6b37a-6b6c-4633-840b-d55e6efb0b1e :: what hooks are enabled for this project

By confirming Yes, Claude will execute the prompt and display results as shown in the illustration below:


Claude Prompt Result
Figure.5

BOOM - we have successfully implemented our first Claude hook !!!

Now, let us shift gears to implement another hook handler - this one for the PreToolUse event, which is triggered before Claude executes any tool. Note that this hook handler can be used to block the tool execution by Claude code.

The JSON input for a PreToolUse hook event will have the following fields (in addition to others):

The following is the Python code to intercept and process the tool execution before Claude executes it:


tool-intercept.py
#!/usr/bin/env python3

import json
import sys

DENY_COMMANDS = ['rm -rf', 'unlink']

def tool_intercept():
    # Read JSON data from stdin
    input_data = json.load(sys.stdin)   
    
    # Extract fields - tool_name, tool_input 
    tool_name = input_data.get('tool_name', 'NONE')
    tool_input = input_data.get('tool_input', {})

    if tool_name == 'Bash':
        # Extract command
        command = tool_input.get('command', 'NONE')

        # Check command the deny list
        for cmd in DENY_COMMANDS:
            if cmd in command.lower():
                print(f'BLOCKED: Denying the execution of the command -> {command}', file=sys.stderr)
                sys.exit(2) # prevent Claude from executing
    
    sys.exit(0) # allow Claude to proceed

if __name__ == '__main__':
    tool_intercept()

We need to register the just created hook command as the handler for the PreToolUse hook event in the settings.json file. The following should be the modified contents of the settings.json file as shown below:


.claude/settings.json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/tool-intercept.py"
          }
        ]
      }
    ],
    "UserPromptSubmit": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/log-prompt.sh"
          }
        ]
      }
    ]
  }
}

In the end, the folder structure along with the files for our first hook for PreToolUse is as shown in the illustration below:


Hooks Folder Structure 2
Figure.6

At the Claude prompt, type the following command and press the ENTER key:


> /hooks


The Claude menu options appear as shown in the illustration below:


Claude Hook Options 2
Figure.7

Notice the highlighted menu options - we have our two hooks - one for PreToolUse and the other for UserPromptSubmit !!!

Press the ESC key to exit our custom skill actions menu options.

At the Claude prompt, type the following prompt and press the ENTER key:


> need to clean up so execute the command unlink /tmp/test


Claude Code will proceed to process the request and will be blocked as shown in the illustration below:


Claude Request Blocked
Figure.8

BINGO - with this, we conclude the Part 5 of the Claude adventure series !!!


References

Claude Code Adventures - Part 4

Claude Code Adventures - Part 3

Claude Code Adventures - Part 2

Claude Code Adventures - Part 1

Claude Hooks Documentation



© PolarSPARC