2026/07/17 更新:通知腳本改為參數化,通知文字會顯示是哪一套 CLI。

2026/07/12 更新:新增 Antigravity CLI hooks 設定。

Claude Code、Cursor、Codex,這些 AI coding agent,相信你已經耳熟能詳,甚至每天都在用了。

那你聽過 agent hooks 嗎?

本文將介紹我目前「唯二」使用的 agent hooks,並說明它們為何重要。

涵蓋工具包括 GitHub Copilot、Claude Code、OpenAI Codex、OpenCode CLI 以及 Antigravity CLI。

不過,僅限於 macOS,其餘平台,就靠大家努力了☺️


什麼是 Agent Hooks

引領風騷的 Claude Code 最早發明了 agent hooks 這個概念。因為太過實用,後來其它工具也競相模仿。

簡單說,agent hooks 是這些 AI coding agent 提供的事件機制:當 agent 完成任務、需要確認,或發生特定事件時,自動執行你指定的腳本或指令

如果你用過 Git hooks,比如最常見的 pre-commit hooks——在 commit 前跑腳本,agent hooks 就是同樣的概念:事件發生,執行特定指令

只是觸發點從 Git 操作變成 agent 行為

為什麼不能只靠指令遵循

我們固然可以在 prompt 裡寫下「產完程式碼後記得跑 lint」之類的要求,但 AI 不一定每次都會照做,尤其當上下文愈來愈長、指示愈來愈複雜時,它往往更傾向偷懶

至於「做完記得通知我」就更不用說了,AI 根本沒這能力——它沒辦法「主動」播放音效或彈出系統通知。

而 hooks 保證了確定性——事件發生,腳本執行,不依靠 AI 的自主判斷。甚至做到了 AI 本來不能做的事,比如上述的播放音效🔔

我只設兩種 hook

Agent hooks 能做的事很多,基本上能變成指令、腳本的內容都行,比如自動跑 lint、formatter,把結果寫進檔案等等。

但這些並非本文重點——我自己只設兩種 hook:完成通知權限請求通知,都是為了在 agent 停下來時,能即時提醒我。


為什麼「通知」是最重要的 Hooks

沒有通知,AI 幹活時你只有兩種選擇:在前景盯著它,或不停來回確認。顯然兩種都不太有效率😅

有通知,我們才好真正「非同步」使用它:把任務交給 agent,去做別的事,聽到聲音再回來。

至於 lint、formatter,另有替代方案——pre-commit 這類工具、CI/CD 都能做。但對沒有內建通知機制的工具,只能透過 agent hooks 來達成。

相關文章:Python 開發:pre-commit 設定 Git Hooks 教學

所以,其他 hooks 可以不設,但這個必須有


通用性的偏好

我總共幫五個 AI coding agent 設定過 hooks:GitHub Copilot、OpenAI Codex、Claude Code、OpenCode CLI、Antigravity CLI。

至於 Google 的 Antigravity 2.0 app,有內建通知機制,如果沒用 CLI 可以不必設定。如果同時設定了 hooks 通知,則都會觸發,建議關掉內建通知。

我只寫全域 hooks,不寫 project-level。這些設定放在 home 目錄,透過 yadm 在不同機器之間同步。

相關文章:yadm 教學:實作 macOS 與 Linux 的 dotfiles 跨平台同步

我的 agent skills 也是採取相同策略——幾乎都是全域的,很少寫 project-level。關於「通用 vs 專用」,之前文章有討論過。


接下來直接看設定,腳本也一併附上。

GitHub Copilot:直接共用 Claude Code 設定

GitHub Copilot 能讀取 Claude Code 的設定檔中,有關 hooks 的設定,所以可以直接共用,不一定要另外寫。

不過目前 Copilot 支援的事件種類沒有 Claude Code 多——Stop 事件有支援;不支援的事件會跳過,不會出錯。

考慮到我目前已經暫時棄用 GitHub Copilot 了,這部分請容我略過😆

如有需求,可參考官方文件

Claude Code

設定位置:~/.claude/settings.json,內容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash ~/scripts/mac/notify-agent-stop.sh 'Claude Code'"
}
]
}
],
"PermissionRequest": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash ~/scripts/mac/notify-agent-permission-request.sh 'Claude Code'"
}
]
}
]
}

Stop完成通知:agent 停下來了。

PermissionRequest權限請求通知:當 agent 執行到需要額外權限時,需要你確認才能繼續。這個很有用,因為有時候你離開太久,回來發現 agent 早就在等你,白白浪費了時間。

上面的 command 的 path 請改成你放置腳本的地方,還有檔名。

OpenAI Codex

Codex 的設定對 app 和 CLI 都會生效,下面的 Antigravity 也是如此。

設定位置:~/.codex/config.toml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[hooks]

[[hooks.Stop]]
matcher = ""

[[hooks.Stop.hooks]]
type = "command"
command = "bash ~/scripts/mac/notify-agent-stop.sh Codex"
statusMessage = "Sending agent stop notification"

[[hooks.PermissionRequest]]
matcher = ""

[[hooks.PermissionRequest.hooks]]
type = "command"
command = "bash ~/scripts/mac/notify-agent-permission-request.sh Codex"
statusMessage = "Sending agent permission request notification"

Codex 設定檔採用 TOML 格式,和 Claude Code 的 JSON 語法不同,但結構類似。

第一次設定,重啟 Codex app 時,會需要你確認 hooks 的內容與安全性。

OpenCode CLI

OpenCode 比較特別:不能在設定檔裡寫 hooks,必須用 plugin。

plugin/ 目錄要放在 opencode 設定檔(opencode.json)所在的目錄底下。即:

1
2
3
4
~/.config/opencode/
├── opencode.json
└── plugin/
└── notify.js

本體是一個 js 檔,內容如下(這是我用 AI Vibe 的,你可以自行調整):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export const NotifyPlugin = async ({ $ }) => {
return {
event: async ({ event }) => {
if (process.platform !== "darwin") return

if (event.type === "session.idle") {
await $`bash ~/scripts/mac/notify-agent-stop.sh OpenCode`
}

if (event.type === "permission.asked") {
await $`bash ~/scripts/mac/notify-agent-permission-request.sh OpenCode`
}
}
}
}

另外要注意的是,event type 是 session.idle,不是 Stop。不過意思差不多啦!

透過 Attention 設定

其實,OpenCode CLI 還可以透過 Attention 功能——需要另外寫 tui.json,方便你設定通知,不一定要靠 plugin。

但我不確定它是否適用於「非互動模式」(即 opencode run),而且我感覺這做法的通用性,還不如直接寫 plugin。畢竟通知腳本可以共用。

Antigravity CLI

Antigravity CLI 是 Google 用來取代 Gemini CLI 的命令列工具。所以它的部分設定檔位置,還是延用了 Gemini CLI。

Google 真是狠,財大氣粗,說換就換😆

設定位置:~/.gemini/config/hooks.json,內容如下:

1
2
3
4
5
6
7
8
9
10
11
{
"notify-stop": {
"Stop": [
{
"type": "command",
"command": "bash /Users/kyo/scripts/mac/notify-agent-stop.sh Antigravity; echo '{}'",
"timeout": 30
}
]
}
}

幾個注意事項:

  1. 必須用絕對路徑:Antigravity 在 -p 模式下,相對路徑會失敗
  2. 腳本必須輸出合法 JSON:Antigravity 會檢查 stdout,所以結尾補上 ; echo '{}'
  3. 沒有 PermissionRequest:Antigravity 目前不支援這個事件

Antigravity CLI 已將舊 Gemini CLI 的多個 hook,精簡成僅剩 5 個核心事件。所以沒有 PermissionRequest——但 5 個也太少了吧!


共用的通知腳本

上述工具的 hooks 都呼叫同一組 shell 腳本。腳本接受第一個參數作為 CLI 名稱,這樣多個 agent 並行時,看到通知就知道該回去哪個工具,不用一個個檢查。

沒傳參數時,則顯示預設的 Agent。程式碼如下:

notify-agent-stop.sh

1
2
3
4
5
6
7
8
9
#!/bin/bash

agent_name="${1:-Agent}"

# 播放 Glass 音效
afplay /System/Library/Sounds/Glass.aiff &

# 顯示 Notification Center 通知
osascript -e "display notification \"${agent_name} 已回覆\" with title \"Agent Stop\"" &

afplay 是 macOS 內建的指令,而音效名稱 Glass 也是內建的。

可以到系統的「設定 > 聲音 > 提示聲」去聆聽每一種內建音效,換成自己喜歡的。

也可以用 afplay 播放任何路徑下的音效檔(如 mp3、wav),只要檔案存在,指令就能正常運作。

osascript 則能在通知中心發出文字提醒。這兩個指令搭配起來,就能在任務完成時,同時播放音效和彈出通知,讓你知道 agent 已經處理完畢。

覺得這樣有點吵的話,可以二選一就好。

notify-agent-permission-request.sh

1
2
3
4
5
6
7
8
9
#!/bin/bash

agent_name="${1:-Agent}"

# 播放 Funk 音效
afplay /System/Library/Sounds/Funk.aiff &

# 顯示 Notification Center 通知
osascript -e "display notification \"${agent_name} 需要權限確認\" with title \"Agent Permission Request\"" &

我覺得 Funk 音效很適合當「確認」使用XD


工具會換,需求長存

儘管不同的工具有不同的設定,但「通知」這個需求是一樣的。

所以我在每個工具中都實作了它,讓核心行為保持一致

如此一來,你想換工具或退掉其中一部分時,體驗上不會受到太大影響。你只需要維護一組通知腳本,所有 agent 都能共用。

畢竟,工具只是工具,不要依戀它