BASHCAT
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Claude Code Hooks 完整教學:讓 AI 助手自動化你的開發流程 ![hooks-tutorial-cover](https://hackmd.io/_uploads/rkLkNguNWe.jpg) 那天我又忘了格式化代碼。 說起來有點丟臉,但這種事真的常發生。用 Claude Code 寫完一段功能,測試通過,開心地準備提交,結果 CI 紅了——ESLint 報了一堆格式問題。我只好默默地跑 `prettier --write`,然後再提交一次。 更慘的是有一次,我讓 Claude 幫忙改一個配置,結果它「順手」把 `.env` 文件也改了。雖然我的 `.env` 有在 `.gitignore` 裡,但當下那個心跳漏拍的感覺,你懂的。 後來我才知道,這些問題其實都有一個優雅的解決方案:**Hooks**。 ## 什麼是 Hooks?用管家來比喻 怎麼解釋 Hooks 呢?想像你請了一個超級能幹的 AI 助手(就是 Claude),但這個助手有時候會太有創意,或者忘記一些你很在意的細節。 Hooks 就像是你設定的一套「家規」或者說「自動化管家」。它會在特定時機自動執行你預設的動作: - Claude 要寫入文件**之前**,管家先檢查:「等等,這是不是敏感文件?」 - Claude 寫完文件**之後**,管家自動跑:「來,我幫你格式化一下。」 - Claude 完成整個任務**的時候**,管家通知你:「老闆,活幹完了!」 ![hooks-tutorial-pipeline](https://hackmd.io/_uploads/H1kxVxO4-l.jpg) 關鍵在於:Hooks 是**確定性**的。 這很重要。AI 模型是機率性的——你告訴 Claude「記得格式化代碼」,它大部分時候會記得,但偶爾就是會忘。而 Hooks 不一樣,只要你設定了,它就一定會執行。不是「應該會」,是「一定會」。 這個差別,在團隊協作或者處理敏感操作時,真的很關鍵。 ## Hook 事件:在對的時機做對的事 Claude Code 目前支援這些 Hook 事件,每個都有它適合的使用場景: **PreToolUse** 是最常用的之一。它在 Claude 執行任何工具(寫檔案、跑命令等)**之前**觸發。厲害的是,它可以**阻止**這個操作。想像它是一個門衛,看到可疑的人可以直接擋在門外。 **PostToolUse** 則是在工具執行**之後**觸發。它沒辦法阻止操作(因為已經執行了),但很適合做後續處理,像是自動格式化、跑測試、寫日誌這類事情。 **Stop** 在 Claude 完成整個回應時觸發。適合做最後的驗證或者發送通知。 還有一些其他的:**UserPromptSubmit**(用戶提交提示時)、**SessionStart**(會話開始時)、**Notification**(發送通知時)等等。不過剛入門的話,先掌握前三個就夠用了。 ## 動手配置第一個 Hook 好,說了這麼多,來實際操作一下。 配置 Hooks 有兩種方式。如果你喜歡圖形介面,可以在 Claude Code 裡直接輸入 `/hooks`,它會引導你一步步設定。但我個人更喜歡直接編輯設定檔,因為可以版本控制,也方便在團隊間分享。 設定檔的位置有三個選擇: - `~/.claude/settings.json`:全域設定,所有專案都會套用 - `.claude/settings.json`:專案設定,可以 commit 到 repo 跟團隊共用 - `.claude/settings.local.json`:本地設定,通常放在 `.gitignore` 裡 來寫一個最簡單的 Hook:每次 Claude 編輯 TypeScript 文件後,自動跑 Prettier 格式化。 ```json { "hooks": { "PostToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "jq -r '.tool_input.file_path' | xargs -I {} sh -c 'echo {} | grep -q \"\\.ts$\" && npx prettier --write {}'" } ] } ] } } ``` 這段設定做了什麼?當 Claude 使用 `Write` 或 `Edit` 工具後(`matcher` 用 `|` 表示「或」),它會讀取被操作的文件路徑,如果是 `.ts` 結尾的文件,就跑 Prettier 格式化。 看起來有點複雜?讓我拆解一下那個 command: 1. `jq -r '.tool_input.file_path'`:從 Hook 收到的 JSON 資料中提取文件路徑 2. `xargs -I {} sh -c '...'`:把路徑傳給後面的 shell 命令 3. `grep -q "\\.ts$"`:檢查是不是 TypeScript 文件 4. `npx prettier --write {}`:如果是的話,格式化它 其實如果邏輯稍微複雜一點,我建議直接寫成獨立的 shell 腳本,然後在 Hook 裡呼叫它。比較好維護,也比較好除錯。 ![hooks-tutorial-transform](https://hackmd.io/_uploads/H1eWEgu4bl.jpg) ## 實戰案例:我每天都在用的三個 Hook ### 案例一:保護敏感文件 這個是我設定的第一個 Hook,來自那次 `.env` 被誤改的教訓。 ```json { "hooks": { "PreToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "python3 -c \"import json,sys; d=json.load(sys.stdin); p=d.get('tool_input',{}).get('file_path',''); sys.exit(2 if any(x in p for x in ['.env','.pem','.key','secrets']) else 0)\"" } ] } ] } } ``` 這段程式會檢查 Claude 想要寫入的文件路徑,如果包含 `.env`、`.pem`、`.key` 或 `secrets` 這些關鍵字,就會回傳 exit code 2,阻止操作。 對了,exit code 很重要: - **0**:允許操作繼續 - **2**:阻止操作,並把 stderr 的內容回饋給 Claude 所以你可以在腳本裡輸出一些提示訊息到 stderr,Claude 會看到並理解為什麼被阻止。 ### 案例二:自動跑測試 每次改完代碼手動跑測試?太累了。 ```json { "hooks": { "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": "npm test 2>&1 | head -50 || true" } ] } ] } } ``` 這個 Hook 在 Claude 完成回應時觸發,自動跑測試。`head -50` 是為了避免輸出太長,`|| true` 是確保即使測試失敗也不會阻塞 Claude。 如果你只想在特定文件被修改時才跑測試,可以用 PostToolUse 搭配更精細的條件判斷。 ### 案例三:完成通知 這個真的超實用。有時候讓 Claude 跑一個比較長的任務,我會切去做別的事,但又怕錯過它完成的時機。 macOS 用戶可以這樣設: ```json { "hooks": { "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": "osascript -e 'display notification \"任務完成!\" with title \"Claude Code\"'" } ] } ] } } ``` Linux 用戶可以用 `notify-send`: ```bash notify-send "Claude Code" "任務完成!" ``` 每次 Claude 完成回應,系統通知就會跳出來。再也不用一直盯著終端機了。 ## 踩坑經驗:我替你試過的那些錯 ### 小心無限循環 這個坑我踩過。假設你設了一個 PostToolUse Hook,在 Claude 寫入文件後自動格式化。格式化工具本身也會「寫入」文件,這會不會又觸發 Hook? 好消息是,Claude Code 有做處理,Hook 觸發的操作不會再次觸發 Hook。但如果你的 Hook 邏輯比較複雜,還是要小心。 ### 性能考量 Hook 是同步執行的,意思是 Claude 會等 Hook 跑完才繼續。如果你的 Hook 要跑很久(比如完整的測試套件),Claude 就會卡在那邊。 解法是讓 Hook 腳本在背景執行,或者只跑快速的檢查。比如只跑被修改文件相關的測試,而不是整個測試套件。 ### 除錯技巧 Hook 沒有如預期執行?試試這幾招: 1. 用 `claude --debug` 啟動,可以看到詳細的 Hook 執行日誌 2. 在 Hook 腳本裡加 log,輸出到一個文件 3. 先用簡單的 `echo` 命令測試,確認 Hook 有被觸發 ```json { "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "echo \"Hook triggered at $(date)\" >> /tmp/hook-debug.log" } ] } ] } } ``` 跑幾次操作後,檢查 `/tmp/hook-debug.log` 看看有沒有內容。 ### Matcher 的語法 Matcher 支援幾種寫法: - 精確匹配:`"Bash"` 只匹配 Bash 工具 - 正則表達式:`"Write|Edit"` 匹配 Write 或 Edit - 萬用:`""` 或不設定,匹配所有工具 如果你的 Hook 沒觸發,先檢查 matcher 是不是寫對了。 ## 進階玩法:Prompt 型 Hook 除了 command 類型,Hook 還支援 prompt 類型。這種 Hook 不是執行 shell 命令,而是讓 Claude 用 LLM 來決定怎麼處理。 ```json { "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "prompt", "prompt": "檢查這個 bash 命令是否安全。如果包含 rm -rf、sudo 或其他危險操作,請阻止並解釋原因。" } ] } ] } } ``` 這種方式更靈活,可以處理不好用規則定義的情況。但相對的,它會消耗額外的 token,而且因為是 LLM 判斷,所以結果可能不是 100% 確定性的。 我的建議是:能用 command 就用 command,規則清楚又快。只有真的需要「理解」內容時,才考慮 prompt 型 Hook。 ## 團隊協作:分享你的 Hook 配置 如果你在團隊裡工作,可以把 Hook 配置放在專案的 `.claude/settings.json` 裡,然後 commit 到 repo。這樣團隊成員都會自動套用相同的規則。 但要注意一點:確保 Hook 裡用到的工具(比如 prettier、eslint)在大家的環境裡都有安裝。不然 Hook 會執行失敗,可能造成困惑。 一個好的做法是在專案的 README 或 CLAUDE.md 裡說明: ```markdown ## Claude Code 設定 本專案使用 Claude Code Hooks 自動化以下流程: - 編輯 TypeScript 文件後自動格式化 - 阻止寫入敏感配置文件 - 任務完成時發送通知 請確保已安裝:`npm install -D prettier eslint` ``` ## 該你動手了 說了這麼多,最好的學習方式還是動手試試看。 我建議從最簡單的開始:設一個 PostToolUse Hook,在 Claude 編輯文件後 echo 一句話。確認它有觸發之後,再慢慢加入你真正想要的邏輯。 ```json { "hooks": { "PostToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "echo '文件已更新' && date" } ] } ] } } ``` 先從這個開始,然後根據你的實際需求慢慢擴展。 Hooks 真的是 Claude Code 裡一個被低估的功能。一旦你開始用了,就會發現很多以前需要手動做的事情,現在都可以自動化了。那種「設定好就不用再管」的感覺,真的很爽。 如果你設定了什麼有趣的 Hook,歡迎分享出來。這種小工具的妙用,往往是社群互相交流才會發現的。 --- ## 參考資源 - [Claude Code Hooks 官方文件](https://code.claude.com/docs/en/hooks) - [Hooks 入門指南](https://code.claude.com/docs/en/hooks-guide) - [GitButler: Automate Your AI Workflows with Claude Code Hooks](https://blog.gitbutler.com/automate-your-ai-workflows-with-claude-code-hooks) - [GitHub: claude-code-hooks-mastery](https://github.com/disler/claude-code-hooks-mastery) ---

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully