惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Hacker News
The Hacker News
Google Online Security Blog
Google Online Security Blog
K
Kaspersky official blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Schneier on Security
C
Cybersecurity and Infrastructure Security Agency CISA
Security Archives - TechRepublic
Security Archives - TechRepublic
Hacker News - Newest:
Hacker News - Newest: "LLM"
Cisco Talos Blog
Cisco Talos Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Cyberwarzone
Cyberwarzone
L
LINUX DO - 最新话题
PCI Perspectives
PCI Perspectives
酷 壳 – CoolShell
酷 壳 – CoolShell
云风的 BLOG
云风的 BLOG
N
News and Events Feed by Topic
N
News and Events Feed by Topic
V
Vulnerabilities – Threatpost
T
Troy Hunt's Blog
GbyAI
GbyAI
C
CERT Recently Published Vulnerability Notes
G
Google Developers Blog
Microsoft Azure Blog
Microsoft Azure Blog
量子位
Scott Helme
Scott Helme
月光博客
月光博客
Attack and Defense Labs
Attack and Defense Labs
aimingoo的专栏
aimingoo的专栏
博客园 - 聂微东
Project Zero
Project Zero
G
GRAHAM CLULEY
博客园 - 【当耐特】
Recorded Future
Recorded Future
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
小众软件
小众软件
D
DataBreaches.Net
T
The Blog of Author Tim Ferriss
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
O
OpenAI News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
Stack Overflow Blog
Stack Overflow Blog
爱范儿
爱范儿
S
Security @ Cisco Blogs
The Last Watchdog
The Last Watchdog
MongoDB | Blog
MongoDB | Blog
H
Hacker News: Front Page
Latest news
Latest news
P
Proofpoint News Feed

Hacker News

Introducing Claude Opus 4.7 Qwen Studio The Future of Everything is Lies, I Guess: Where Do We Go From Here? GitHub - SeanFDZ/macmind: Single-layer transformer in HyperTalk for the classic Macintosh Show HN: Agent-cache – Multi-tier LLM/tool/session caching for Valkey and Redis Moving a large-scale metrics pipeline from StatsD to OpenTelemetry / Prometheus GitHub - Nightmare-Eclipse/RedSun: The Red Sun vulnerability repository GitHub - SethPyle376/hiraeth: Local AWS emulator focused on fast integration testing, with SQS support, SQLite-backed state, and a debug-friendly web UI. GitHub - macOS26/Agent: Any AI, replaces Claude Code, Cursor, OpenClaw. Over 18 LLM providers (Claude, OpenAI, Gemini, Ollama, Zai, HF, Qwen) wired into a native Mac app that writes code, builds Xcode projects, bumps versions, manages git, automates Safari, use AppleScript, JS or Accessibility, extend Agent! w/ MCP Servers, run tasks from your iPhone via Messages. YouTube now lets you turn off Shorts I Made a Terminal Pager Burgers | マクドナルド公式 Commands — HackerNews CLI documentation ChatGPT for Excel PiCore - Raspberry Pi Port of Tiny Core Linux Live Nation illegally monopolized ticketing market, jury finds Google Broke Its Promise to Me. Now ICE Has My Data. Founding Engineer at Adaptional | Y Combinator CRISPR takes important step toward silencing Down syndrome’s extra chromosome GitHub - saffron-health/libretto: The AI toolkit for building reliable browser automations US v. Heppner (S.D.N.Y. 2026) no attorney-client privilege for AI chats [pdf] Unexpected €54k billing spike in 13 hours: Firebase browser key without API restrictions used for Gemini requests Retrofitting JIT Compilers into C Interpreters IPv6 – Google The Accursèd Alphabetical Clock Cybersecurity Looks Like Proof of Work Now Fragments: April 14 Cal.com Goes Closed Source: Why AI Security Is Forcing Our Decision | Cal.com - Scheduling Software for Online Bookings Laravel raised money and now injects ads directly into your agent When moving fast, talking is the first thing to break Too much Discussion of the XOR swap trick – Heather Cafe Introduction to Spherical Harmonics for Graphics Programmers The Grand Line Building a Z-Machine in the worst possible language High-Level Rust: Getting 80% of the Benefits with 20% of the Pain GitHub - duguyue100/midnight-captain: Inspired by Midnight Commander, tailored to my taste. How to build a `git diff` driver · Jamie Tanna | Software Engineer Center for Responsible, Decentralized Intelligence at Berkeley The Local Universe’s Expansion Rate Is Clearer Than Ever, but Still Doesn’t Add Up - A new synthesis of astronomical measurements confirms a persistent mismatch that could point to physics beyond current models The air throughout our homes is infused with microplastics. But there are things you can do to breathe less of them The disturbing white paper Red Hat is trying to erase from the internet – OSnews The Future of Everything is Lies, I Guess: Annoyances ‘Abhorrent’: the inside story of the Polymarket gamblers betting millions on war Productive procrastination — Max van IJsselmuiden maps, territory and LMs 447 Terabytes per Square Centimetre at Zero Retention Energy: Non-Volatile Memory at the Atomic Scale on Fluorographane Show HN: Pardonned.com – A searchable database of US Pardons 20 Years on AWS and Never Not My Job The Seasons are Wrong Artemis II crew splashes down near San Diego after historic moon mission We gave an AI a 3 year retail lease in SF and asked it to make a profit | Andon Labs How a dancer with ALS used brainwaves to perform live On filing the corners off my MacBooks Installing every* Firefox extension OpenClaw’s memory is unreliable, and you don’t know when it will break Steve Blank Nowhere Is Safe Chimpanzees in Uganda locked in vicious 'civil war', say researchers watgo - a WebAssembly Toolkit for Go linux/Documentation/process/coding-assistants.rst at master · torvalds/linux GitHub - callumlocke/json-formatter: Makes JSON easy to read. Founding Product Engineer at Bild AI | Y Combinator A compelling title that is cryptic enough to get you to take action on it GitHub - Keychron/Keychron-Keyboards-Hardware-Design: Industrial design files for Keychron keyboards and mice. 100+ models with CAD assets in STEP, DXF, DWG, and PDF. Source-available, with commercial use allowed for original compatible accessories within the license terms. [ANNOUNCE] WireGuardNT v0.11 and WireGuard for Windows v0.6 Released 1D-Chess Helium Is Hard to Replace Cooperative Vectors Introduction | Evolve Keeping a Postgres queue healthy — PlanetScale Our response to the Axios developer tool compromise Do Americans read print books, e-books or audiobooks more? The Zettelkasten Method in Obsidian: A Practical Setup Guide Artemis II Is Competency Porn and We Are Starving For It WeakC4 Flight Viz — Cockpit View A Mexican surveillance giant you’ve never heard of is now watching the U.S. border Surelock: Deadlock-Free Mutexes for Rust RISC-V 101 – what is it and what does it mean for Canonical? | Ubuntu The Problem That Built an Industry How Much Linear Memory Access Is Enough? | Solidean Investigating Split Locks on x86-64 Simplest hash functions Sybilproof reputation mechanisms (2005) [pdf] What is a property? How Complex is my Code? Static code analysis in Kotlin — tools overview Toffoli gates are all you need PGLite evangelism dcmake: a new CMake debugger UI Clojure on Fennel part one: Persistent Data Structures Fragments: April 2 Python Release Python install manager 26.1 The Life and Death of the Book Review - Liberties Introducing Database Traffic Control — PlanetScale Bitcoin miners are losing $19,000 on every BTC produced as difficulty drops 7.8% God sleeps in the minerals Building slogbox Apple Silicon and Virtual Machines: Beating the 2 VM Limit Who was “Not Even Wrong” first? Pokemon Evolution Vs Darwinian Evolution The APL Programming Language Source Code
GitHub - RasmusGodske/claude-hook-utils
2026-05-29 · via Hacker News

A Python utility package for building Claude Code hooks with minimal boilerplate.

What Are Claude Code Hooks?

Claude Code hooks are custom scripts that run at specific points during Claude Code's execution. They allow you to:

  • Validate tool calls before they execute (PreToolUse)
  • React to tool results after execution (PostToolUse)
  • Intercept user prompts before Claude sees them (UserPromptSubmit)
  • Initialize state when a session starts (SessionStart)

Why This Package?

Building Claude Code hooks involves repetitive boilerplate:

  • Parsing JSON from stdin
  • Validating input structure
  • Formatting responses in the correct schema
  • Handling errors gracefully

claude-hook-utils handles all of this, letting you focus on your validation logic.

Design Philosophy

  1. One Pattern - Extend HookHandler, override the hooks you need
  2. Type Safety - Typed dataclasses for inputs, builder pattern for responses
  3. Explicit Control - Helper methods on inputs, but you decide when to skip/allow/deny
  4. Multi-Hook Support - One Python program can handle multiple hook types
  5. No Heavy Dependencies - Core package has minimal dependencies; bring your own AI SDK if needed

Installation

pip install claude-hook-utils

Quick Start

#!/usr/bin/env python3
"""Validate that Data classes have TypeScript annotation."""

from claude_hook_utils import HookHandler, PreToolUseInput, PreToolUseResponse

class DataClassValidator(HookHandler):
    def pre_tool_use(self, input: PreToolUseInput) -> PreToolUseResponse | None:
        # Skip if not a Data class file
        if not input.file_path_matches('**/app/Data/**/*.php'):
            return None

        # Check for required annotation
        if input.content and '#[TypeScript()]' not in input.content:
            return PreToolUseResponse.deny(
                "Data classes must have #[TypeScript()] annotation for type generation"
            )

        return PreToolUseResponse.allow()

if __name__ == "__main__":
    DataClassValidator().run()

Configure in .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "python3 /path/to/data_class_validator.py"
          }
        ]
      }
    ]
  }
}

Supported Hook Types

Hook Type When It Runs Use Cases
PreToolUse Before a tool executes Validate file paths, check content, block dangerous operations
PostToolUse After a tool completes Log results, trigger follow-up actions, collect metrics
UserPromptSubmit When user submits a prompt Validate prompts, add context, enforce policies
SessionStart When a Claude Code session begins Initialize state, set environment variables

API Reference

HookHandler Base Class

Extend this class and override the hooks you need:

from claude_hook_utils import HookHandler

class MyHandler(HookHandler):
    def __init__(self):
        super().__init__()
        # Add any shared state here
        self._cache: dict = {}

    def pre_tool_use(self, input: PreToolUseInput) -> PreToolUseResponse | None:
        """Called before tool execution. Return None to skip."""
        return None

    def post_tool_use(self, input: PostToolUseInput) -> PostToolUseResponse | None:
        """Called after tool execution. Return None to skip."""
        return None

    def user_prompt_submit(self, input: UserPromptSubmitInput) -> UserPromptSubmitResponse | None:
        """Called when user submits a prompt. Return None to skip."""
        return None

    def session_start(self, input: SessionStartInput) -> SessionStartResponse | None:
        """Called when session starts. Return None to skip."""
        return None

if __name__ == "__main__":
    MyHandler().run()

PreToolUseInput

Input for PreToolUse hooks:

@dataclass
class PreToolUseInput:
    # Common fields
    session_id: str
    cwd: str
    hook_event_name: str  # Always "PreToolUse"

    # PreToolUse-specific
    tool_name: str        # "Write", "Edit", "Bash", etc.
    tool_input: dict      # Tool-specific parameters
    tool_use_id: str

    # Helper methods
    def file_path_matches(self, *globs: str) -> bool:
        """Check if tool_input.file_path matches any glob pattern."""

    def file_path_excludes(self, *globs: str) -> bool:
        """Check if tool_input.file_path does NOT match any glob pattern."""

    # Convenience properties
    @property
    def file_path(self) -> str | None:
        """Get file_path from tool_input (for Write/Edit/Read tools)."""

    @property
    def content(self) -> str | None:
        """Get content from tool_input (for Write tool)."""

    @property
    def command(self) -> str | None:
        """Get command from tool_input (for Bash tool)."""

PreToolUseResponse

Response builder for PreToolUse hooks:

class PreToolUseResponse:
    @staticmethod
    def allow(reason: str | None = None) -> PreToolUseResponse:
        """Allow the tool to execute."""

    @staticmethod
    def deny(reason: str) -> PreToolUseResponse:
        """Block the tool. Reason is shown to Claude as feedback."""

    @staticmethod
    def ask(reason: str) -> PreToolUseResponse:
        """Request user confirmation before proceeding."""

    def with_updated_input(self, **updates) -> PreToolUseResponse:
        """Modify tool_input before execution (only valid with allow)."""

HookLogger

JSONL-based logging for easy debugging. Logs are organized by namespace (plugin name).

from claude_hook_utils import HookHandler, HookLogger

class MyHandler(HookHandler):
    def __init__(self):
        # Logs to .claude/logs/my-plugin/hooks.jsonl
        super().__init__(
            logger=HookLogger.create_default("MyHandler", namespace="my-plugin")
        )

    def pre_tool_use(self, input: PreToolUseInput) -> PreToolUseResponse | None:
        # Session ID is automatically added from input
        self.logger.info("Checking file", file_path=input.file_path)
        # ... validation logic ...
        self.logger.decision("allow", reason="Validation passed")
        return PreToolUseResponse.allow()

Log format (JSONL - one JSON object per line):

{"ts": "2025-01-04T10:15:23.456+00:00", "level": "INFO", "hook": "MyHandler", "namespace": "my-plugin", "session": "abc123", "msg": "Checking file", "file_path": "/path/to/file.php"}
{"ts": "2025-01-04T10:15:23.458+00:00", "level": "DECISION", "hook": "MyHandler", "namespace": "my-plugin", "session": "abc123", "msg": "decision=allow", "decision": "allow", "reason": "Validation passed"}

Configuration:

  • Default location: {cwd}/.claude/logs/{namespace}/hooks.jsonl
  • Without namespace: {cwd}/.claude/logs/hooks.jsonl
  • Override directory with CLAUDE_HOOK_LOG_DIR env var
  • Override namespace with CLAUDE_HOOK_LOG_NAMESPACE env var
  • Session ID is automatically extracted from hook input

Examples

Validate Vue Component Structure

class VueValidator(HookHandler):
    def pre_tool_use(self, input: PreToolUseInput) -> PreToolUseResponse | None:
        if not input.file_path_matches('**/*.vue'):
            return None

        content = input.content or ''

        # Check tag order: <script> before <template> before <style>
        script_pos = content.find('<script')
        template_pos = content.find('<template')
        style_pos = content.find('<style')

        if script_pos > template_pos or template_pos > style_pos:
            return PreToolUseResponse.deny(
                "Vue components must have tags in order: <script>, <template>, <style>"
            )

        # Check for setup lang="ts"
        if '<script setup lang="ts">' not in content:
            return PreToolUseResponse.deny(
                "Vue components must use <script setup lang=\"ts\">"
            )

        return PreToolUseResponse.allow()

Validate Controller Location

class ControllerValidator(HookHandler):
    def pre_tool_use(self, input: PreToolUseInput) -> PreToolUseResponse | None:
        if not input.file_path_matches('**/*Controller.php'):
            return None

        # Controllers must be in app/Http/Controllers/
        if not input.file_path_matches('**/app/Http/Controllers/**/*.php'):
            return PreToolUseResponse.deny(
                f"Controllers must be in app/Http/Controllers/. "
                f"Found: {input.file_path}"
            )

        return PreToolUseResponse.allow()

Block FormRequest Usage (Suggest Data Class)

class NoFormRequestValidator(HookHandler):
    def pre_tool_use(self, input: PreToolUseInput) -> PreToolUseResponse | None:
        if not input.file_path_matches('**/*Controller.php'):
            return None

        content = input.content or ''

        if 'FormRequest' in content:
            return PreToolUseResponse.deny(
                "Do not use FormRequest classes. Use Data classes instead. "
                "See: app/Data/ for examples."
            )

        return PreToolUseResponse.allow()

Multi-Hook Handler (Pre + Post)

class FileTracker(HookHandler):
    def __init__(self):
        super().__init__()
        self._pending_writes: set[str] = set()

    def pre_tool_use(self, input: PreToolUseInput) -> PreToolUseResponse | None:
        if input.tool_name == 'Write' and input.file_path:
            self._pending_writes.add(input.file_path)
            self.logger.info(f"Tracking write: {input.file_path}")
        return PreToolUseResponse.allow()

    def post_tool_use(self, input: PostToolUseInput) -> PostToolUseResponse | None:
        if input.tool_name == 'Write' and input.file_path:
            self._pending_writes.discard(input.file_path)
            self.logger.info(f"Write completed: {input.file_path}")
        return None

Claude Code Hook Response Format

This package generates responses in the official hookSpecificOutput format:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Your reason here"
  }
}

Decision Options

Decision Effect
allow Tool executes immediately, reason shown to user
deny Tool blocked, reason shown to Claude (so it can adapt)
ask User confirmation dialog shown

Modifying Tool Input

Use with_updated_input() to modify parameters before execution:

def pre_tool_use(self, input: PreToolUseInput) -> PreToolUseResponse | None:
    # Auto-correct a common mistake
    if input.file_path and '/data/' in input.file_path:
        corrected = input.file_path.replace('/data/', '/Data/')
        return PreToolUseResponse.allow("Auto-corrected path").with_updated_input(
            file_path=corrected
        )
    return PreToolUseResponse.allow()

Error Handling

The package handles errors gracefully:

  • Invalid JSON input: Returns exit 0 (no output = allow)
  • Unknown hook type: Returns None (skip)
  • Exception in handler: Logged to stderr, returns exit 0 (fail open)

This "fail open" approach ensures your hooks don't block Claude Code if something goes wrong.

Environment Variables

Claude Code provides these environment variables to hooks:

Variable Description
CLAUDE_PROJECT_DIR Absolute path to project root
CLAUDE_CODE_REMOTE "true" if running in web environment

This package uses:

Variable Description
CLAUDE_HOOK_LOG_DIR Override default log directory (default: .claude/logs/{namespace}/)
CLAUDE_HOOK_LOG_NAMESPACE Override log namespace/subdirectory

Access via input.cwd or os.environ.

Extending for New Hook Types

To add support for a new hook type:

  1. Create input dataclass in inputs/
  2. Create response class in responses/
  3. Add handler method to HookHandler
  4. Add dispatch case in HookHandler._dispatch()

See existing implementations for patterns to follow.

License

MIT