Hooks — Kiểm soát tất định hành vi Claude Code

Hệ sinh thái mở rộngTrung cấp30 phút

Một dev backend ở startup fintech đã viết vào CLAUDE.md:

Bạn sẽ học được
  • Giải thích hook là gì và tại sao nó deterministic (tất định) trong khi CLAUDE.md và skill thì probabilistic (xác suất)
  • Nắm rõ 5 lifecycle events: PreToolUse, PostToolUse, UserPromptSubmit, Stop, Notification — và khi nào dùng cái nào
  • Cấu hình hooks đúng cách trong settings.json với matcher, command, và script
  • Dùng exit codes (0 / 2) để allow hoặc block tool call từ PreToolUse hook
  • Share hooks với cả team bằng cách commit settings.json và script vào repo

Hook là gì?

Hook là một script chạy tự động tại các điểm cụ thể trong lifecycle của Claude Code. Bạn cấu hình hook trong settings.json — khai báo: event nào, tool nào (matcher), và command nào cần chạy.

Điểm cốt lõi phân biệt hook với mọi cơ chế khác:

Hook không phụ thuộc vào "ký ức" hay "quyết định" của model. Đó là lý do duy nhất khiến nó deterministic.

Lifecycle của Claude Code — nơi hooks can thiệp

Cơ chếTính chấtAi quyết định chạy?
HookDeterministic — luôn chạyHệ thống Claude Code (không phải model)
CLAUDE.mdProbabilisticModel đọc, model quyết định
SkillProbabilisticModel nhận request, model quyết định invoke
MCPAvailable-when-neededModel quyết định gọi tool
┌─────────────────────────────────────────────────────────────────────┐
│                    CLAUDE CODE LIFECYCLE                            │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  User gõ prompt                                                     │
│       │                                                             │
│       ▼                                                             │
│  ┌─────────────┐                                                    │
│  │UserPromptSubmit│ ◄── Hook chạy ở đây (trước khi Claude xử lý)   │
│  └──────┬──────┘                                                    │
│         │                                                           │
│         ▼                                                           │
│   Claude xử lý, quyết định gọi tool                                │
│         │                                                           │
│         ▼                                                           │
│  ┌─────────────┐                                                    │
│  │ PreToolUse  │ ◄── Hook chạy ở đây (có thể BLOCK tool call)      │
│  └──────┬──────┘                                                    │
│         │  exit 0 → allow / exit 2 → block                         │
│         ▼                                                           │
│   Tool call thực thi (Edit, Bash, Read, v.v.)                       │
│         │                                                           │
│         ▼                                                           │
│  ┌─────────────┐                                                    │
│  │ PostToolUse │ ◄── Hook chạy ở đây (format, log, test)           │
│  └──────┬──────┘                                                    │
│         │                                                           │
│         ▼                                                           │
│   Claude tiếp tục loop hoặc kết thúc                               │
│         │                                                           │
│         ▼                                                           │
│  ┌─────────────┐                                                    │
│  │    Stop     │ ◄── Hook chạy ở đây (notify, cleanup)             │
│  └─────────────┘                                                    │
│                                                                     │
│  ┌─────────────────┐                                                │
│  │  Notification   │ ◄── Hook chạy khi Claude send notification     │
│  └─────────────────┘                                                │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

5 Hook Events

Lưu ý: Matcher là regex match trên tên tool (Edit, Bash, Write, v.v.). PreToolUse và PostToolUse thường cần matcher để tránh hook chạy trên mọi tool call.

EventChạy khi nàoCó thể block?Typical use caseCần matcher?
PreToolUseTrước khi tool call thực thiCó (exit 2)Block write vào prod file, block rm -rf, validate inputKhuyến nghị
PostToolUseSau khi tool call hoàn thànhKhôngAuto-format code, chạy lint, ghi audit log, trigger testKhuyến nghị
UserPromptSubmitKhi user submit prompt, trước khi Claude xử lýKhông trực tiếpLog prompt, inject thêm context, validate prompt formatKhông bắt buộc
StopKhi Claude hoàn thành responseKhôngGửi Slack notification, cleanup temp files, trigger deployKhông bắt buộc
NotificationKhi Claude gửi notificationKhôngCustom notification routing, macOS say, Slack pingKhông bắt buộc

Anatomy của hook config

Hook được cấu hình trong .claude/settings.json (project-level, có thể commit vào repo) hoặc ~/.claude/settings.json (user-level, áp dụng mọi project).

Cấu trúc JSON

Giải thích từng thành phần

Script nhận gì từ Claude Code?

Script của bạn nhận JSON qua stdin chứa thông tin về event:

  • Event key (PostToolUse, PreToolUse, v.v.) — lifecycle event bạn muốn hook vào
  • matcher — regex string match với tên tool. "Edit|MultiEdit|Write" nghĩa là hook chỉ chạy khi Claude dùng một trong 3 tools này. Để trống "" nghĩa là match mọi tool call
  • type — hiện tại chỉ có "command" (chạy shell command)
  • command — lệnh shell hoặc đường dẫn tới script. Dùng $CLAUDE_PROJECT_DIR thay vì hardcode absolute path
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|MultiEdit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format-on-edit.sh"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/block-dangerous-commands.sh"
          }
        ]
      }
    ],
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/notify-done.sh"
          }
        ]
      }
    ]
  }
}

Script nhận gì từ Claude Code?

Script output ra stdout/stderr, và exit code là tín hiệu quan trọng nhất.

{
  "tool_name": "Edit",
  "tool_input": {
    "file_path": "/path/to/file.ts",
    "old_string": "const x = 1",
    "new_string": "const x = 2"
  }
}

Exit Codes và Behavior

Quan trọng: Khi PreToolUse hook exit với code 2, Claude nhận được stderr message như một "giải thích tại sao bị từ chối". Claude sẽ tự điều chỉnh — ví dụ thay đổi approach hoặc hỏi lại bạn. Đây là cơ chế feedback loop thông minh.

Exit codeBehaviorUse caseVí dụ
0Proceed normally — tool call được allow hoặc PostToolUse tiếp tụcMọi trường hợp bình thườngScript chạy Prettier thành công
2Block tool call (chỉ valid cho PreToolUse). stderr message được feed back cho Claude như feedbackEnforce hard rulesBlock write vào infra/prod/*
Khác (1, 3, ...)Non-blocking error — hiển thị cho user nhưng không dừng ClaudeScript lỗi nhưng không muốn blockPrettier không tìm thấy, bỏ qua
PreToolUse hook exit 2
      │
      ▼
Claude nhận stderr: "BLOCKED: Cannot write to infra/prod/ — use staging only"
      │
      ▼
Claude tự điều chỉnh: "Tôi sẽ thử tạo file ở infra/staging/ thay thế"

Ví dụ thực chiến: Auto-format hook

Đây là hook phổ biến nhất — auto-format file sau mỗi lần Claude edit.

Bước 1: Tạo script

Tạo file .claude/hooks/format-on-edit.sh:

Bước 2: Cấp quyền thực thi

#!/usr/bin/env bash
# Hook: Auto-format file after Claude edits it
# Receives JSON on stdin with tool_input.file_path

set -euo pipefail

# Đọc JSON từ stdin, extract file_path
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_input',{}).get('file_path',''))" 2>/dev/null || echo "")

# Nếu không có file path, thoát bình thường
if [ -z "$FILE_PATH" ]; then
  exit 0
fi

# Nếu file không tồn tại, thoát bình thường
if [ ! -f "$FILE_PATH" ]; then
  exit 0
fi

# Xác định formatter theo extension
EXT="${FILE_PATH##*.}"

case "$EXT" in
  ts|tsx|js|jsx|json|css|html|md)
    # Prettier cho web stack
    if command -v prettier &>/dev/null; then
      prettier --write "$FILE_PATH" --log-level warn
      echo "Formatted (prettier): $FILE_PATH" >&2
    fi
    ;;
  go)
    # gofmt cho Go
    if command -v gofmt &>/dev/null; then
      gofmt -w "$FILE_PATH"
      echo "Formatted (gofmt): $FILE_PATH" >&2
    fi
    ;;
  py)
    # ruff cho Python (nhanh hơn black)
    if command -v ruff &>/dev/null; then
      ruff format "$FILE_PATH" --quiet
      echo "Formatted (ruff): $FILE_PATH" >&2
    elif command -v black &>/dev/null; then
      black "$FILE_PATH" --quiet
      echo "Formatted (black): $FILE_PATH" >&2
    fi
    ;;
  *)
    # Extension không hỗ trợ — thoát bình thường
    exit 0
    ;;
esac

exit 0

Bước 2: Cấp quyền thực thi

Bước 3: Thêm vào settings.json

chmod +x .claude/hooks/format-on-edit.sh

Bước 3: Thêm vào settings.json

Bước 4: Commit vào repo

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|MultiEdit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format-on-edit.sh"
          }
        ]
      }
    ]
  }
}

Bước 4: Commit vào repo

Toàn bộ team clone repo về sẽ tự động có hook này.

Bước 5: Test

git add .claude/hooks/format-on-edit.sh .claude/settings.json
git commit -m "chore: add auto-format hook for Claude Code"

Bước 5: Test

Claude edit file. Ngay sau khi edit xong, hook chạy Prettier. File được format trước khi Claude tiếp tục bất kỳ bước nào. Không cần nhắc, không có 20% fail rate.

> Sửa file src/components/Button.tsx — đổi className "btn" thành "button"

Blocking với PreToolUse

PreToolUse hook là cơ chế enforce "hard rules" — những điều phải được đảm bảo, không phải chỉ "đề xuất".

Script nhận gì?

Ví dụ: Block lệnh Bash nguy hiểm

Tạo .claude/hooks/block-dangerous-commands.sh:

{
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf /tmp/old-build"
  }
}

Ví dụ: Block lệnh Bash nguy hiểm

Config trong settings.json:

#!/usr/bin/env bash
# Hook: Block dangerous bash commands in PreToolUse
# exit 0 = allow, exit 2 = block (stderr → Claude feedback)

set -euo pipefail

INPUT=$(cat)
TOOL=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_name',''))" 2>/dev/null || echo "")
COMMAND=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))" 2>/dev/null || echo "")

# Chỉ check tool Bash
if [ "$TOOL" != "Bash" ]; then
  exit 0
fi

# RULE 1: Block rm -rf (quá nguy hiểm, không ngoại lệ)
if echo "$COMMAND" | grep -qE 'rm\s+-[a-zA-Z]*r[a-zA-Z]*f|rm\s+-[a-zA-Z]*f[a-zA-Z]*r'; then
  echo "BLOCKED: 'rm -rf' không được phép. Dùng 'rm -r' kèm đường dẫn cụ thể, hoặc xác nhận với user trước." >&2
  exit 2
fi

# RULE 2: Block write/modify vào infra/prod/
if echo "$COMMAND" | grep -qE '(infra/prod|production\.env|\.env\.prod)'; then
  echo "BLOCKED: Không được thao tác file production trực tiếp. Chỉ dùng infra/staging/." >&2
  exit 2
fi

# RULE 3: Block git push --force
if echo "$COMMAND" | grep -qE 'git push.*--force|git push.*-f\b'; then
  echo "BLOCKED: force push không được phép. Tạo PR thay thế." >&2
  exit 2
fi

# Cho phép mọi lệnh khác
exit 0

Blocking với PreToolUse (tiếp)

Khi Claude cố chạy rm -rf /tmp/cache:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/block-dangerous-commands.sh"
          }
        ]
      },
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/block-prod-writes.sh"
          }
        ]
      }
    ]
  }
}

Blocking với PreToolUse (tiếp)

[Hook blocked: BLOCKED: 'rm -rf' không được phép. Dùng 'rm -r' kèm đường dẫn cụ thể...]
Claude: "Tôi hiểu rồi. Tôi sẽ dùng 'rm -r /tmp/cache' thay thế để xoá thư mục một cách an toàn hơn."

Hook vs Skill vs CLAUDE.md vs MCP

Đây là bảng so sánh quan trọng nhất trong bài — quyết định khi nào dùng cơ chế nào:

Quy tắc quyết định đơn giản:

Cross-reference: xem thêm Bài 2.9 (Skills) và Bài 2.7 (CLAUDE.md) để so sánh chi tiết hơn.

  • Nếu bạn cần đảm bảo điều gì đó xảy ra → Hook
  • Nếu bạn muốn Claude biết cách làm gì đó khi bạn hỏi → Skill
  • Nếu bạn muốn Claude luôn nhớ context chung của project → CLAUDE.md
  • Nếu bạn cần Claude truy cập hệ thống ngoài → MCP
Tiêu chíHookSkillCLAUDE.mdMCP
Tính chấtDeterministic — luôn chạyProbabilistic — model quyết địnhProbabilistic — model đọc, có thể quênAvailable-when-needed
TriggerEvent trong lifecycleModel match request với skill nameLoad lúc session start, model đọcModel quyết định gọi tool
VisibilityScript ngoài contextLoad vào context khi matchLuôn trong contextTool definition trong context
Best forGuarantees — phải xảy ra 100%Patterns — workflow tái sử dụngProject-wide rules — conventions, commandsExternal integrations — databases, APIs
Ví dụAuto-format mỗi edit/commit với chuẩn commit message"Dùng pnpm, không npm"GitHub, Linear, Slack
OverheadChạy script ngoàiLoad markdown vào contextLuôn có trong contextTool defs trong context

Case studies theo role

Backend Engineer: Lint guarantee

Mỗi lần Claude edit bất kỳ file .ts, PostToolUse hook auto-chạy eslint --fix. Không bao giờ có CI lint failure nữa — dù session dài 4 tiếng hay Claude đang cuối context.

DevOps / Security Engineer: Prod file protection

PreToolUse hook block mọi write vào infra/prod/. Claude chỉ được phép thao tác infra/staging/. Block + stderr message rõ ràng giúp Claude tự chọn đường dẫn staging, không cần dev can thiệp.

Engineering Team: Async notification

Stop hook gửi Slack message khi Claude hoàn thành task. Team có thể giao Claude task dài (30+ phút), làm việc khác, nhận Slack ping khi xong. Không cần ngồi nhìn màn hình chờ.

# .claude/hooks/lint-on-edit.sh
FILE_PATH=$(cat | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('file_path',''))")
[[ "$FILE_PATH" == *.ts ]] && npx eslint --fix "$FILE_PATH" --quiet
exit 0

Engineering Team: Async notification

Compliance team: Audit log bắt buộc

PreToolUse hook log mọi Bash command vào file audit với timestamp, user, command. Không có exception. Đây là regulatory requirement mà hook đảm bảo 100% — không thể "quên" như khi viết trong CLAUDE.md.

# .claude/hooks/notify-slack.sh
curl -s -X POST "$SLACK_WEBHOOK_URL" \
  -H 'Content-type: application/json' \
  --data '{"text":"Claude finished the task. Ready for review."}' > /dev/null
exit 0

Compliance team: Audit log bắt buộc

Open Source Maintainer: Test trước commit

PostToolUse hook chạy test suite sau mỗi lần Claude edit file source (không phải test file). Nếu test fail, script log ra stderr — Claude biết có regression và tự fix trước khi tiếp tục.

Solo founder: macOS notification

Notification hook chạy say "Claude xong rồi" (macOS text-to-speech). Bạn có thể rời bàn phím, về máy sẽ nghe thông báo khi Claude hoàn thành task background.

# .claude/hooks/audit-log.sh
COMMAND=$(cat | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))")
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | USER=$USER | CMD=$COMMAND" >> /var/log/claude-audit.log
exit 0

Solo founder: macOS notification

# .claude/hooks/say-done.sh
say "Claude xong rồi, về review đi bạn ơi" &
exit 0

Anti-patterns

Script chạy quá lâu (>10 giây)

Hook là synchronous trong lifecycle. Nếu script mất 30 giây (ví dụ chạy full test suite mỗi edit), mỗi tool call của Claude sẽ bị block 30 giây. UX cực kỳ tệ. Giải pháp: chạy test nhanh, hoặc dùng background process (&) với Stop hook thay vì PostToolUse.

PreToolUse không xử lý input rỗng

Nếu tool call không có field bạn expect (ví dụ Bash không có command khi là subshell), script crash → false-positive block. Luôn handle gracefully: || echo "" và check empty trước khi process.

Hardcode absolute path trong command

CLAUDE_PROJECT_DIR là env var Claude Code inject vào khi chạy hook — luôn trỏ đến root của project hiện tại.

Không commit settings.json và script vào repo

Nếu chỉ có bạn có hook, team sẽ commit code không format, không lint. Mất đi toàn bộ lợi ích "deterministic for the whole team". Hook chỉ thực sự mạnh khi toàn team có cùng config.

Hook chứa secret / API key

Script được commit vào repo. Nếu script hardcode SLACK_TOKEN=xoxb-..., bạn vừa leak secret. Dùng environment variable: $SLACK_WEBHOOK_URL và set trong môi trường local / CI secrets.

Dùng hook cho việc nên là Skill

Hook là cho guarantees. Nếu bạn viết hook để "nhắc Claude viết commit message đúng chuẩn", đó là việc của Skill hoặc CLAUDE.md — không cần guarantee mỗi tool call. Hook không cần thiết = overhead vô nghĩa.

Matcher quá rộng (no matcher)

// SAI — vỡ khi teammate clone về
"command": "/Users/john/projects/myapp/.claude/hooks/format.sh"

// ĐÚNG — work ở bất kỳ machine nào
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format.sh"

Matcher quá rộng (no matcher)

Matcher rỗng nghĩa là hook chạy mỗi lần Claude gọi bất kỳ tool nào. Với PostToolUse formatter, điều này vô nghĩa và chậm.

// BAD — hook chạy trên MỌI tool call (Read, Glob, Grep, v.v.)
{ "matcher": "" }

// GOOD — chỉ chạy khi Claude edit file
{ "matcher": "Edit|MultiEdit|Write" }

Mẹo nâng cao

Dùng CLAUDE_PROJECT_DIR cho mọi path reference

Env var này được inject tự động bởi Claude Code. Script của bạn work bất kể cwd của Claude đang ở đâu trong project.

Combine hooks: PreToolUse → PostToolUse → Stop pipeline

# Trong script
LOG_FILE="$CLAUDE_PROJECT_DIR/logs/claude-audit.log"
CONFIG="$CLAUDE_PROJECT_DIR/.env.local"

Combine hooks: PreToolUse → PostToolUse → Stop pipeline

Ba hooks phối hợp tạo một pipeline hoàn chỉnh: validate trước, cleanup sau, notify khi xong.

Idempotent scripts — chạy nhiều lần không có side effect

Prettier, gofmt, ruff đều idempotent (chạy nhiều lần ra cùng kết quả). Tuy nhiên nếu script của bạn append vào file log, đảm bảo format đủ thông tin để avoid duplicate entries gây nhầm lẫn.

Test hook isolation trước khi enable

PreToolUse: validate input (block nếu cần)
     ↓
PostToolUse: format + lint + log
     ↓
Stop: notify team qua Slack

Test hook isolation trước khi enable

Chạy script trực tiếp với mock stdin trước khi enable trong settings.json. Dễ debug hơn nhiều so với enable rồi chạy Claude.

Conditional logic trong script (không chỉ dựa vào matcher)

Matcher chỉ match tên tool. Script có thể implement logic phức tạp hơn:

# Tạo mock input
echo '{"tool_name":"Edit","tool_input":{"file_path":"/tmp/test.ts"}}' \
  | .claude/hooks/format-on-edit.sh

Conditional logic trong script (không chỉ dựa vào matcher)

Debug: comment tạm matcher để isolate hook behavior

Khi debug Claude behavior, bạn nghi hook đang can thiệp. Cách nhanh nhất: đổi matcher thành một string không bao giờ match ("DISABLED_Edit|MultiEdit"). Claude behavior trở về "thuần" — so sánh để xác nhận hook là culprit.

Multiple matchers trong 1 entry

# Chỉ format nếu file trong src/ (không format test fixtures)
if [[ "$FILE_PATH" == */src/* ]]; then
  prettier --write "$FILE_PATH"
fi

Multiple matchers trong 1 entry

Một hook entry cover 3 tools. Thực tế với file editing, bạn muốn cover cả 3: Edit (single block), MultiEdit (nhiều blocks), Write (tạo file mới).

{
  "matcher": "Edit|MultiEdit|Write",
  "hooks": [...]
}

Áp dụng ngay

Bài tập 1: Auto-format hook (~25 phút)

Tạo PostToolUse hook auto-format cho project của bạn.

Bước 1: Xác định formatter bạn đang dùng (Prettier, eslint, ruff, gofmt, v.v.)

Bước 2: Tạo .claude/hooks/format-on-edit.sh (xem script ở phần trên, adapt cho stack của bạn)

Bước 3: Thêm vào .claude/settings.json:

Bước 4: Test script isolation với mock input trước

Bước 5: Nhờ Claude edit 5 file khác nhau. Verify mỗi lần đều format. Không có ngoại lệ.

Bước 6: Commit cả script + settings vào repo

Bài tập 2: PreToolUse blocking hook (~20 phút)

Tạo hook block 1 dangerous pattern trong project của bạn.

Chọn 1 trong các scenario:

Bước 1: Tạo .claude/hooks/block-dangerous.sh với logic check và exit 2

Bước 2: Test reject case: tạo mock input với command nguy hiểm, verify script exit 2 với đúng stderr message

Bước 3: Test allow case: mock input với command bình thường, verify script exit 0

Bước 4: Enable trong settings.json, test với Claude thực tế

Bonus: Thử nhờ Claude làm action bị block → xem Claude tự adjust như thế nào dựa vào stderr feedback

  • Block rm -rf trong Bash commands
  • Block write vào .env files
  • Block edit files trong infra/prod/
  • Block git push --force
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|MultiEdit|Write",
        "hooks": [{"type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format-on-edit.sh"}]
      }
    ]
  }
}

Tóm tắt

Hook là cơ chế duy nhất trong Claude Code mà hành vi là deterministic — không phụ thuộc vào model "nhớ" hay "quyết định". Đây là công cụ để bạn enforce hard rules, không phải đề xuất.

5 takeaways cốt lõi:

Quote đáng nhớ:

  • Hook chạy tại lifecycle events — script thông thường, không có technology mới để học
  • PostToolUse cho guarantees sau action: format, lint, log, test — chạy mỗi lần không trừ
  • PreToolUse cho blocking: exit 2 + stderr message → Claude tự điều chỉnh
  • Stop / Notification cho async notifications — để làm việc khác, nhận ping khi Claude xong
  • Commit settings.json + script vào repo → toàn team có cùng hook, không ai bị "thiếu"
Tài liệu tham khảo
  • Claude Code Hooks documentation — Anthropic official docs
  • Claude Code settings.json reference — cấu trúc đầy đủ
  • /hooks command — gõ trong Claude Code session để xem và edit hooks interactively
  • CLAUDE_PROJECT_DIR env var — inject tự động bởi Claude Code khi chạy hook script
Nội dung này có hữu ích không?