iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0
Vibe Coding

老闆不會教你的 Vibe Coding 實戰 30 天系列 第 22 篇

老闆不會教你的 Vibe Coding 實戰 30 天|Day 22:Hooks 生命週期觸發器

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20261004/20119486TbIuAzNBAb.png

前言

接下來即將進入 Week4 也是整個系列的最後一週。

那這一週開始我們要做什麼呢?這邊我要來介紹一個 AI 的「生命週期觸發器」:Hooks。

它的概念是掛在 AI 的生命週期上,在特定的時機去觸發特定的指令,這個過程並不會有任何 AI 的判斷,完全是系統層級的自動化。

Hook 的概念:生命週期

其實 AI 中有一個所謂的「生命週期」的概念,我知道看到這邊可能許多人會有點覺得...

「蛤?生命週期?AI 也有生命週期?」

簡單來講,AI 在進行任務的過程中,會經歷一些特定的事件,例如:

  • 工具執行前(PreToolUse)
  • 工具執行後(PostToolUse)
  • AI 每次結束回應時(Stop)
  • 需要你注意時(Notification)

這四個基本上是 AI 最常用的生命週期事件,當這些事件發生時,我們就可以掛上我們的 Hook,讓它自動執行我們想要的指令。

而這些 Hook 的設定,則是放在專案的設定檔中,通常是 settings.json 的 hooks 區塊中。

那 settings.json 是什麼呢?其實我們前面一直沒有特別聊到它,它就是 AI 的設定檔,權限、Hook 這些設定都是放在這裡面,而它跟 Day 15 講的 SKILL 一樣,分成兩層:

  • 放在你電腦的(~/.claude/settings.json): 你自己所有專案通用。
  • 放在專案的(.claude/settings.json): 只有這個專案用,而且會跟著專案一起 commit,所以隊友拉下來也會有一樣的設定。

另外還有一個 .claude/settings.local.json,一樣是放在專案裡,可是它是給你個人用的,不會 commit 進 Git,等一下第二個 Hook 我們就會用到它。

那看到這邊你可能會想說,這跟直接在 CLAUDE.md 寫規則有什麼不一樣?

大概差別在於「拜託你做」跟「強制你做」的差異吧。

不好懂?前面我們有介紹過 CLAUDE.md 給 AI 看的規則、憲法,每一次開啟新的對話時,它都會載入,但其實儘管說它是憲法、規範,但還是有大機率會被遺忘,尤其是 Context 太長的時候,所以 CLAUDE.md 比較偏向「拜託你做」的概念,AI 會盡量遵守,但不保證每次都記得。

但 Hook 則是建立在系統層級上的自動化機關,只要 AI 執行到特定行為、事件,就必定會觸發 Hook,完全不經過 AI 的判斷,所以它的強制性就比 CLAUDE.md 高上很多。

那常見哪些時候呢?最簡單的就是「改完檔案要排版」,這種每次都一定要做的事情,就很適合用 Hook 而沒有必要經過 AI 的判斷。

https://ithelp.ithome.com.tw/upload/images/20261004/20119486s3hMalXp2w.png

所以接下來我們將會來實際體驗兩個 Hook 的實作,一個是共享的 PostToolUse 排版 Hook,另一個是個人的 Stop 桌面通知 Hook。

Note
其實你會發現我一直反覆強調 Context 的重要性,不管你用的是哪個方案,甚至是打 API 的方式,AI 最昂貴的永遠都是 Token 以及 Context 的妥善使用。

實作一:改完檔案就自動排版

接下來我們要來體驗一下 Hook 的威力,先從最簡單的排版開始。

首先這邊會需要先做一件事情,你要先替 money-note 專案建立一個 .claude/hooks 資料夾,而這個資料夾是用來放 Hook 腳本的。

接著我們要替這個專案安裝一個新套件,叫做 Prettier,它是一個程式碼排版工具,會依照固定的規則幫你整理縮排、引號、分號這些東西,讓專案裡的程式碼長得一致,所以一樣請你打開終端機並移動到這個專案下,接著輸入以下指令:

npm install -D prettier

當然,你也可以懶一點,輸入以下 Prompt 讓 AI 幫你安裝:

請在 money-note 專案安裝 Prettier,並把它加到 devDependencies 裡。

Note
如果你的專案已經有裝 Prettier,這一步跳過就好。

在貼程式碼之前,先說明一下這個 Hook 會如何運作:

  1. AI 使用 Edit 或 Write 工具改了一個檔案。
  2. 改完的瞬間,PostToolUse 這個事件被觸發,AI 會去 settings.json 找有沒有掛在這個事件上的 Hook。
  3. 如果有掛東西就會執行 Hook 裡面的指令,並且把「剛剛發生了什麼事」寫成一份 JSON 報告交給那個指令,JSON 裡面會有這次用的是哪個工具、改的是哪個檔案。
  4. 接著我們自定的指令會接收到 JSON,並從裡面拿到檔案路徑,接著對那個檔案跑 Prettier。

這邊我也順手畫成流程圖:

https://ithelp.ithome.com.tw/upload/images/20261004/201194865xrpqhM8CP.png

所以我們要準備兩個東西:

  • 一個接收 JSON 並跑 Prettier 的腳本。
  • 一個告訴 AI「PostToolUse 時要去跑這支腳本」的設定。

接著請你在專案底下建立 .claude/hooks/format-edited-file.mjs 這個檔案,並貼上以下內容(當然你也可以直接把這段丟給 AI,請它幫你建立):

import { spawnSync } from 'node:child_process'
import { isAbsolute, relative, resolve } from 'node:path'

let raw = ''
for await (const chunk of process.stdin) raw += chunk

try {
  const input = JSON.parse(raw)
  const filePath = input.tool_input?.file_path
  if (!filePath) process.exit(0)

  const projectRoot = resolve(process.env.CLAUDE_PROJECT_DIR || process.cwd())
  const target = isAbsolute(filePath) ? resolve(filePath) : resolve(projectRoot, filePath)
  const pathFromRoot = relative(projectRoot, target)

  if (pathFromRoot.startsWith('..') || isAbsolute(pathFromRoot)) process.exit(0)

  const prettierBin = resolve(projectRoot, 'node_modules/prettier/bin/prettier.cjs')
  const result = spawnSync(process.execPath, [prettierBin, '--write', '--ignore-unknown', target], {
    cwd: projectRoot,
    stdio: ['ignore', 'inherit', 'inherit'],
  })

  process.exit(result.status ?? 0)
} catch (error) {
  console.error('格式化 Hook 無法讀取輸入:', error.message)
  process.exit(1)
}

上面程式碼你不用太花時間看,基本上就只是做三件事情:

  • 把第 3 步 AI 交過來的 JSON 收下來,接著找出這是修改的檔案,也就是 tool_input.file_path。
  • 確認那個檔案真的在專案資料夾裡面。
  • 對那一個檔案跑 Prettier。

所以接著我們就要來把 Hook 的腳本掛上去(.claude/settings.json),讓它在 PostToolUse 事件發生時自動執行,內容如下:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "node \"${CLAUDE_PROJECT_DIR}/.claude/hooks/format-edited-file.mjs\""
          }
        ]
      }
    ]
  }
}

很短吧?這邊不用擔心,我也會一一解釋:

  • PostToolUse: 生命週期事件,也就是 工具執行完之後 觸發。
  • matcher: 建立過濾條件,也就是只有 Edit 跟 Write 這兩個改檔工具跑完才觸發,其餘的讀取檔案 or 執行指令都不會觸發。
  • command: 想要執行的指令,這邊是使用 Node 去跑剛剛那支腳本,${CLAUDE_PROJECT_DIR} 是 Claude Code 提供的變數,代表專案的根目錄。

基本上儲存之後 Claude Code 會自己偵測到設定檔有變動,不過保險起見你也可以重開一次,確保它真的有把設定讀進來。

那我們該如何測試呢?很簡單,輸入以下 Prompt 給 AI:

請用 Write 工具建立 src/hook-test.js,裡面隨便寫一個小函式,縮排跟空格故意亂排、不要加分號,直接寫進去就好,不用檢查也不用排版。

你應該會看到它寫的很亂很糟:

https://ithelp.ithome.com.tw/upload/images/20261004/20119486J6jcn5wIKF.png

但...實際上你打開 src/hook-test.js,你會發現排版已經被整理得整整齊齊了

https://ithelp.ithome.com.tw/upload/images/20261004/20119486JTRuBWTKf0.png

Note
這邊 Prompt 我特別寫「用 Write 工具」是有原因的,因為 Hook 的 matcher 只會看 Edit 跟 Write 這兩個工具,如果 AI 是用 Bash 指令(像 cat > 檔案)把檔案寫出來,那...Hook 就不會跑,所以才刻意指定 Write 工具。

實作二:做完通知我

接下來這個 Hook 算是連我個人都會使用的 Hook,畢竟我滿常丟一些需要跑比較久的任務給 AI,但跑的過程我總不可能一直盯著終端機看吧?!肯定會善用時間去做一些零碎且可以被中斷的事情,所以我就有針對 Hooks 寫一個 Stop Hook,當 AI 每次結束回應時,就跳一個桌面通知給我。

那...這個範例要特別提醒一下,這是專門於 macOS 的,所以建議你不要用在 Windows 或 Linux,因為它們的桌面通知指令不一樣,通常這類通知也比較偏向「個人」,所以會建議放在 .claude/settings.local.json,不要放在共享的 .claude/settings.json,不然隊友拉下來就會跳通知給他們 xD

所以請你建立 .claude/settings.local.json,貼上以下內容:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"money-note 的任務完成了\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

Note
osascript 是 macOS 的專屬指令,除此之外,Stop 沒有 matcher 可以設定,所以這邊直接寫 hooks 就可以囉。

這邊有件事情也要提醒一下,通常 .gitignore 並不會把 .claude/settings.local.json 排除在外,因為它是手動建立的,所以請你自己把它加進 .gitignore,避免不小心 commit 進 Git。

把上面 Hook 加入之後,我們就可以來試著觸發看看了,你可以隨便輸入個 Prompt 讓 AI 幫你改一個檔案,像是:

Hello!今天你好嗎?

不出意外的話,你就會得到一個 macOS 桌面通知,告訴你「money-note 的任務完成了」。

https://ithelp.ithome.com.tw/upload/images/20261004/20119486NfUyORQyMr.png

很簡單吧?Hook 非常的強大,我甚至還把它接到我的 Discord 頻道上呢。

Hooks 同時是雙面刃

那到這邊為止,也要同時提醒一件事情,儘管 Hook 非常強大,它的價值也建立於強制性上,但同時這也是一個風險。

什麼意思呢?當 CLAUDE.md 的規則有問題或者寫錯時,AI 可以自己判斷去修正、去繞過去,但如果你 Hook 出現錯誤,那麼每次觸發都會爆一次,而且它不會幫你判斷。

所以基本上我會建議你跟 AI 討論之後,請 AI 幫你寫好 Hook 腳本,接著在你自己電腦的終端機先測試過,確認它能正常運作之後,再掛上去,這樣可以大幅度減少一些不必要的問題。

像我們這次的腳本就可以這樣測,丟一份沒有檔案路徑的 JSON 給它,正常的話它會安靜地結束,不會有任何錯誤:

printf '%s' '{"tool_input":{}}' | node .claude/hooks/format-edited-file.mjs

結語

那這章節也介紹差不多了,也一樣總結一下吧!

  • Hook 是掛在 AI 生命週期上的機關,事件一發生就執行、不經過 AI 判斷,CLAUDE.md 是拜託你做、Hook 是強制你做。
  • Hook 的設定寫在 settings.json 的 hooks 區塊,共享的放專案的 .claude/settings.json 會跟著 commit,個人的(像桌面通知)放 .claude/settings.local.json,記得自己加進 .gitignore。
  • matcher 決定哪些工具會觸發,這次只盯 Edit 跟 Write,所以 AI 用 Bash 寫檔是不會觸發的。
  • Hook 是強制的,指令寫錯每次觸發都會爆一次,所以先在終端機測過再掛上去。

那明天我們就來聊測試,Day 14 說過「錯了不痛可以先放生」的那些 code,該有人幫你盯著了。

希望這一篇有讓你對 Hook 有更進一步的認識哩~

我們明天 Day 23 見囉~


上一篇
老闆不會教你的 Vibe Coding 實戰 30 天|Day 21:Week 3 收尾之所以 Skills、Subagents、MCP 怎麼選
下一篇
老闆不會教你的 Vibe Coding 實戰 30 天|Day 23:測試安全網
系列文
老闆不會教你的 Vibe Coding 實戰 30 天 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言