Claude Code 利用 Event-Driven Hooks 打造自動化開發大腦

在現代 AI 輔助開發中,我們不僅需要 AI 寫程式,更需要它懂規則、記性好,並且能自動處理那些繁瑣的雜事。透過 Claude Code Hooks 機制,我們可以介入 AI 的思考與執行迴圈,實現真正的「人機協作自動化」。
一、 動機與痛點:為什麼你需要介入 AI 的生命週期?
在預設狀態下,Claude Code 雖然強大,但它是「被動」且「無狀態」的,這導致了開發者常遇到以下痛點:
記憶重置 (Session Amnesia):
痛點:每次重啟終端機,AI 就像失憶一樣。
解法:你需要一個機制,在
SessionStart時自動把「上一集的劇情(Session Log)」灌輸給它。
程式碼品質不一 (Inconsistent Quality):
痛點:AI 寫出的 Go 程式碼可能忘了
gofmt,或者留下了fmt.Println除錯訊息。解法:你需要一個「糾察隊」,在
PostToolUse(工具用完後)自動執行格式化與檢查。
危險操作 (Safety Risks):
痛點:AI 有時會過度自信,想直接
git push到主分支。解法:你需要在
PreToolUse(工具執行前)設下攔截點,強制顯示警告。
上下文丟失 (Context Drift):
痛點:對話太長時,重要資訊被壓縮丟棄。
解法:利用
PreCompact在壓縮發生前,將關鍵狀態寫入硬碟。
二、 核心機制:生命週期圖解 (The Lifecycle)
要掌握 Hooks,必須理解這張生命週期圖。這不僅是流程,更是我們可以「插入程式碼」的機會點:

| Hook 事件 | 觸發時機 | 應用場景 |
SessionStart | Claude Code 啟動時 | 載入上次進度、顯示專案狀態 |
SessionEnd | 會話結束時 | 保存工作進度、建立 session 記錄 |
PreToolUse | 執行工具前 | 安全檢查、阻擋危險操作 |
PostToolUse | 執行工具後 | 程式碼格式化、品質檢查 |
Stop | AI 完成回應時 | 檢查未提交的 debug 程式碼 |
PreCompact | Context 壓縮前 | 保存重要狀態、記錄壓縮事件 |
我們可以將其劃分為三個戰略區域:
1. 啟動與結束區 (Session Management)
SessionStart:這是「載入記憶」的時刻。你的腳本session-start.js在這裡執行,負責掃描.claude/sessions/下的.tmp檔案,告訴 AI 上次工作到哪裡。SessionEnd:這是「存檔」的時刻。session-end.js會將當前的狀態快照保存下來,供下次使用。
2. 代理循環區 (The Agentic Loop) - 自動化的核心
這是圖中橙色虛線框起來的部分,也是 AI 實際工作的地方。
PreToolUse(攔截層):在 AI 真正執行Bash或Edit之前。這是防止錯誤的最佳時機(例如阻擋git push)。PostToolUse(修正層):在 AI 修改完檔案後。你的腳本可以在這裡自動執行gofmt,或是檢查有沒有遺留的fmt.Print。
3. 維護區 (Maintenance)
PreCompact:當 Token 即將爆滿時,系統會觸發壓縮。利用pre-compact.js記錄這一事件,防止重要資訊在壓縮中「無聲消失」。
三、 配置結構與語法
Hooks 配置在 .claude/settings.local.json 中:
{
"hooks": {
"HookEvent": [
{
"matcher": "條件表達式",
"hooks": [
{
"type": "command",
"command": "要執行的指令"
}
]
}
]
}
}
Matcher 語法
"*"- 匹配所有情況tool == "Bash"- 匹配特定工具tool == "Edit" && tool_input.file_path matches "\\.go$"- 組合條件!(tool_input.file_path matches "README\\.md")- 排除條件
四、 實戰配置解析:你的腳本做了什麼?
結合 Go 專案範例,這套配置實現了以下具體功能:
1. 智慧型記憶掛載 (SessionStart)
你的配置不再只是依賴單一的 CLAUDE.md,而是引入了時間序列的 Session Log。
行為:腳本會檢查
go.mod確認這是 Go 專案,並自動尋找最近修改過的sessions/*.tmp檔案。優勢:AI 一啟動就知道專案類型(Go Module)以及上次具體的工作內容,實現「無縫熱啟動」。
配置範例:
{
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node /path/to/project/.claude/scripts/hooks/session-start.js"
}
]
}
]
}
session-start.js:
#!/usr/bin/env node
const path = require('path');
const fs = require('fs');
function main() {
const sessionsDir = path.join(process.env.HOME, '.claude', 'sessions');
// 檢查是否有最近的 session 記錄
if (fs.existsSync(sessionsDir)) {
const files = fs.readdirSync(sessionsDir)
.filter(f => f.endsWith('.tmp'))
.sort().reverse();
if (files.length > 0) {
console.error(`[SessionStart] Found ${files.length} recent session(s)`);
console.error(`[SessionStart] Latest: ${files[0]}`);
}
}
// Go 專案檢測
if (fs.existsSync('go.mod')) {
const content = fs.readFileSync('go.mod', 'utf8');
const match = content.match(/^module\s+(.+)$/m);
if (match) {
console.error(`[SessionStart] Go project: ${match[1]}`);
}
}
process.exit(0);
}
main();
2. 強制性程式碼規範 (PostToolUse)
這是這套配置最精彩的部分—— 自動修正 (Auto-Correction)。
行為:當監測到
Edit工具修改了.go檔案後,Hooks 會自動觸發:gofmt -w "file_path"同時,若發現檔案內含有
fmt.Print,會透過console.error警告開發者。優勢:即使 AI 生成的程式碼格式混亂,寫入硬碟的那一刻也會被強制修正為標準格式。這大幅減少了 code review 的負擔。
配置範例:
{
"PostToolUse": [
{
"matcher": "tool == \"Edit\" && tool_input.file_path matches \"\\\\.go$\"",
"hooks": [
{
"type": "command",
"command": "node -e \"const{execSync}=require('child_process');const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path;if(p&&fs.existsSync(p)){try{execSync('gofmt -w \\\"'+p+'\\\"',{stdio:['pipe','pipe','pipe']})}catch(e){console.error('[Hook] gofmt failed: '+e.message)}}console.log(d)})\""
}
]
},
{
"matcher": "tool == \"Edit\" && tool_input.file_path matches \"\\\\.go$\"",
"hooks": [
{
"type": "command",
"command": "node -e \"const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path;if(p&&fs.existsSync(p)){const c=fs.readFileSync(p,'utf8');if(/fmt\\\\.Print(ln|f)?\\\\(/.test(c)){console.error('[Hook] WARNING: fmt.Print found in '+p);console.error('[Hook] Consider using proper logging instead')}}console.log(d)})\""
}
]
},
{
"matcher": "tool == \"Bash\"",
"hooks": [
{
"type": "command",
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const cmd=i.tool_input?.command||'';if(/gh pr create/.test(cmd)){const out=i.tool_output?.output||'';const m=out.match(/https:\\\\/\\\\/github.com\\\\/[^/]+\\\\/[^/]+\\\\/pull\\\\/\\\\d+/);if(m){console.error('[Hook] PR created: '+m[0])}}console.log(d)})\""
}
]
}
]
}
3. 危險操作防護網 (PreToolUse)
行為:當 AI 試圖執行
git push時,Hooks 會攔截並輸出:[Hook] Review changes before push... Consider: git diff HEAD~1優勢:增加了一道「冷靜期」,防止 AI 在未經人工確認的情況下將錯誤程式碼推送到遠端倉庫。
配置範例:
{
"PreToolUse": [
{
"matcher": "tool == \"Bash\" && tool_input.command matches \"git push\"",
"hooks": [
{
"type": "command",
"command": "node -e \"console.error('[Hook] Review changes before push...');console.error('[Hook] Consider: git diff HEAD~1, git log --oneline -5')\""
}
]
},
{
"matcher": "tool == \"Write\" && tool_input.file_path matches \"\\\\.(md|txt)$\" && !(tool_input.file_path matches \"README\\\\.md|CLAUDE\\\\.md\")",
"hooks": [
{
"type": "command",
"command": "node -e \"console.error('[Hook] WARNING: Creating documentation file');console.error('[Hook] Consider using CONTEXT.md for session notes')\""
}
]
}
]
}
4. 提交前最後檢查 (Stop)
配置範例:
{
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node -e \"const{execSync}=require('child_process');const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{execSync('git rev-parse --git-dir',{stdio:'pipe'})}catch{console.log(d);process.exit(0)}try{const files=execSync('git diff --name-only HEAD',{encoding:'utf8',stdio:['pipe','pipe','pipe']}).split('\\\\n').filter(f=>/\\\\.go$/.test(f)&&fs.existsSync(f));let hasDebug=false;for(const f of files){const content=fs.readFileSync(f,'utf8');if(/fmt\\\\.Print(ln|f)?\\\\(/.test(content)){console.error('[Hook] WARNING: fmt.Print found in '+f);hasDebug=true}}if(hasDebug)console.error('[Hook] Remove debug prints before committing')}catch(e){}console.log(d)})\""
}
]
}
]
}
5. Session 結束存檔 (SessionEnd)
配置範例:
{
"SessionEnd": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node /path/to/project/.claude/scripts/hooks/session-end.js"
}
]
}
]
}
session-end.js:
#!/usr/bin/env node
const path = require('path');
const fs = require('fs');
function main() {
const sessionsDir = path.join(process.env.HOME, '.claude', 'sessions');
const today = new Date().toISOString().split('T')[0];
const sessionFile = path.join(sessionsDir, `${today}-session.tmp`);
if (!fs.existsSync(sessionsDir)) {
fs.mkdirSync(sessionsDir, { recursive: true });
}
const time = new Date().toTimeString().slice(0, 5);
if (fs.existsSync(sessionFile)) {
let content = fs.readFileSync(sessionFile, 'utf8');
content = content.replace(/\*\*Last Updated:\*\*.*/, `**Last Updated:** ${time}`);
fs.writeFileSync(sessionFile, content);
console.error(`[SessionEnd] Updated session: ${today}-session.tmp`);
} else {
const template = `# Session: ${today}
**Started:** ${time}
**Last Updated:** ${time}
## Completed
- [ ]
## In Progress
- [ ]
## Notes for Next Session
-
`;
fs.writeFileSync(sessionFile, template);
console.error(`[SessionEnd] Created session: ${today}-session.tmp`);
}
process.exit(0);
}
main();
6. 壓縮前狀態保存 (PreCompact)
配置範例:
{
"PreCompact": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node /path/to/project/.claude/scripts/hooks/pre-compact.js"
}
]
}
]
}
pre-compact.js:
#!/usr/bin/env node
const path = require('path');
const fs = require('fs');
function main() {
const sessionsDir = path.join(process.env.HOME, '.claude', 'sessions');
const logFile = path.join(sessionsDir, 'compaction-log.txt');
if (!fs.existsSync(sessionsDir)) {
fs.mkdirSync(sessionsDir, { recursive: true });
}
const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
fs.appendFileSync(logFile, `[${timestamp}] Context compaction triggered\n`);
console.error('[PreCompact] State preserved before compaction');
process.exit(0);
}
main();
五、 目錄結構
建議的專案 hooks 結構:
.claude/
├── settings.local.json # Hooks 配置
├── scripts/
│ ├── hooks/
│ │ ├── session-start.js
│ │ ├── session-end.js
│ │ └── pre-compact.js
│ └── lib/
│ └── utils.js # 共用工具函式
└── skills/ # Claude Code skills
六、 為什麼 Claude Code 仰賴這些設定?
Claude Code 本質上是一個 「事件驅動的執行環境 (Event-Driven Execution Environment)」。
彌補 LLM 的缺陷:LLM 擅長生成,但不擅長「守紀律」和「記狀態」。Hooks 透過確定性的程式碼(Node.js/Shell)來彌補機率性的 AI 模型。
標準輸入/輸出的管道設計: 注意到腳本中使用了
process.stdin和process.stdout嗎?process.stdin.on('data', c => d += c); // 接收 Claude 的數據 console.log(d); // 必須把數據傳下去,否則流程會斷掉這設計讓 Hooks 成為類似 Linux Pipe 的過濾器,可以在不打斷 AI 思路的前提下,對資料進行「偷看」、「修改」或「阻擋」。
七、 注意事項
Hook 必須輸出 stdin 資料:對於需要處理輸入的 hooks,必須在最後輸出
console.log(d)將原始資料傳遞下去使用 stderr 顯示訊息:
console.error()用於顯示給使用者的訊息,console.log()用於傳遞資料避免阻塞:Hook 腳本應快速執行,避免耗時操作
錯誤處理:即使發生錯誤也應
process.exit(0),避免阻斷 Claude Code 流程路徑處理:使用絕對路徑或相對於專案根目錄的路徑
八、 配置後帶來的行為變革
一旦部署這套 .claude/settings.local.json,你的開發體驗將發生如下質變:
| 開發情境 | 觸發設定 (Hook) | 系統自動化行為 | 開發者獲得的好處 |
| 開啟專案 | SessionStart | 自動讀取 sessions/2026-01-24.tmp 並分析 go.mod。 | 秒進狀態:不用再打字解釋「這是 Go 專案,上次做到哪」。 |
| AI 寫完 Code | PostToolUse | 背景靜默執行 gofmt。 | 格式完美:檔案永遠符合 Go Standard,不會有縮排錯誤。 |
| AI 忘記刪 Log | Stop/PostToolUse | 掃描並紅字警告:WARNING: fmt.Print found。 | 保持潔淨:防止 Debug 代碼污染生產環境。 |
| 準備提交 PR | PreToolUse | 攔截 git push 並建議先 git diff。 | 安全防護:避免意外將實驗性代碼推上線。 |
| 關閉終端 | SessionEnd | 將當前進度寫入 *-session.tmp。 | 進度固化:確保今天的上下文能準確傳遞給明天的自己。 |
總結
這套配置將 記憶持久化概念 進一步細化為針對 Go 語言特性的自動化工作流。它不僅解決了「失憶」問題,更透過 gofmt 和安全檢查,讓 AI 成為了一個「守紀律」的初級工程師,而不僅僅是一個聊天機器人。



