Skip to main content

Command Palette

Search for a command to run...

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

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

在現代 AI 輔助開發中,我們不僅需要 AI 寫程式,更需要它懂規則、記性好,並且能自動處理那些繁瑣的雜事。透過 Claude Code Hooks 機制,我們可以介入 AI 的思考與執行迴圈,實現真正的「人機協作自動化」。


一、 動機與痛點:為什麼你需要介入 AI 的生命週期?

在預設狀態下,Claude Code 雖然強大,但它是「被動」且「無狀態」的,這導致了開發者常遇到以下痛點:

  1. 記憶重置 (Session Amnesia)

    • 痛點:每次重啟終端機,AI 就像失憶一樣。

    • 解法:你需要一個機制,在 SessionStart 時自動把「上一集的劇情(Session Log)」灌輸給它。

  2. 程式碼品質不一 (Inconsistent Quality)

    • 痛點:AI 寫出的 Go 程式碼可能忘了 gofmt,或者留下了 fmt.Println 除錯訊息。

    • 解法:你需要一個「糾察隊」,在 PostToolUse(工具用完後)自動執行格式化與檢查。

  3. 危險操作 (Safety Risks)

    • 痛點:AI 有時會過度自信,想直接 git push 到主分支。

    • 解法:你需要在 PreToolUse(工具執行前)設下攔截點,強制顯示警告。

  4. 上下文丟失 (Context Drift)

    • 痛點:對話太長時,重要資訊被壓縮丟棄。

    • 解法:利用 PreCompact 在壓縮發生前,將關鍵狀態寫入硬碟。


二、 核心機制:生命週期圖解 (The Lifecycle)

要掌握 Hooks,必須理解這張生命週期圖。這不僅是流程,更是我們可以「插入程式碼」的機會點:

Hook lifecycle diagram showing the sequence of hooks from SessionStart through the agentic loop to SessionEnd

Hook 事件觸發時機應用場景
SessionStartClaude Code 啟動時載入上次進度、顯示專案狀態
SessionEnd會話結束時保存工作進度、建立 session 記錄
PreToolUse執行工具前安全檢查、阻擋危險操作
PostToolUse執行工具後程式碼格式化、品質檢查
StopAI 完成回應時檢查未提交的 debug 程式碼
PreCompactContext 壓縮前保存重要狀態、記錄壓縮事件

我們可以將其劃分為三個戰略區域:

1. 啟動與結束區 (Session Management)

  • SessionStart:這是「載入記憶」的時刻。你的腳本 session-start.js 在這裡執行,負責掃描 .claude/sessions/ 下的 .tmp 檔案,告訴 AI 上次工作到哪裡。

  • SessionEnd:這是「存檔」的時刻。session-end.js 會將當前的狀態快照保存下來,供下次使用。

2. 代理循環區 (The Agentic Loop) - 自動化的核心

這是圖中橙色虛線框起來的部分,也是 AI 實際工作的地方。

  • PreToolUse (攔截層):在 AI 真正執行 BashEdit 之前。這是防止錯誤的最佳時機(例如阻擋 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)」

  1. 彌補 LLM 的缺陷:LLM 擅長生成,但不擅長「守紀律」和「記狀態」。Hooks 透過確定性的程式碼(Node.js/Shell)來彌補機率性的 AI 模型。

  2. 標準輸入/輸出的管道設計: 注意到腳本中使用了 process.stdinprocess.stdout 嗎?

     process.stdin.on('data', c => d += c); // 接收 Claude 的數據
     console.log(d); // 必須把數據傳下去,否則流程會斷掉
    

    這設計讓 Hooks 成為類似 Linux Pipe 的過濾器,可以在不打斷 AI 思路的前提下,對資料進行「偷看」、「修改」或「阻擋」。


七、 注意事項

  1. Hook 必須輸出 stdin 資料:對於需要處理輸入的 hooks,必須在最後輸出 console.log(d) 將原始資料傳遞下去

  2. 使用 stderr 顯示訊息console.error() 用於顯示給使用者的訊息,console.log() 用於傳遞資料

  3. 避免阻塞:Hook 腳本應快速執行,避免耗時操作

  4. 錯誤處理:即使發生錯誤也應 process.exit(0),避免阻斷 Claude Code 流程

  5. 路徑處理:使用絕對路徑或相對於專案根目錄的路徑


八、 配置後帶來的行為變革

一旦部署這套 .claude/settings.local.json,你的開發體驗將發生如下質變:

開發情境觸發設定 (Hook)系統自動化行為開發者獲得的好處
開啟專案SessionStart自動讀取 sessions/2026-01-24.tmp 並分析 go.mod秒進狀態:不用再打字解釋「這是 Go 專案,上次做到哪」。
AI 寫完 CodePostToolUse背景靜默執行 gofmt格式完美:檔案永遠符合 Go Standard,不會有縮排錯誤。
AI 忘記刪 LogStop/PostToolUse掃描並紅字警告:WARNING: fmt.Print found保持潔淨:防止 Debug 代碼污染生產環境。
準備提交 PRPreToolUse攔截 git push 並建議先 git diff安全防護:避免意外將實驗性代碼推上線。
關閉終端SessionEnd將當前進度寫入 *-session.tmp進度固化:確保今天的上下文能準確傳遞給明天的自己。

總結

這套配置將 記憶持久化概念 進一步細化為針對 Go 語言特性的自動化工作流。它不僅解決了「失憶」問題,更透過 gofmt 和安全檢查,讓 AI 成為了一個「守紀律」的初級工程師,而不僅僅是一個聊天機器人。


參考資源

12 views

More from this blog

工程師的 Claude Code 實戰指南:從零開始到高效開發

工程師的 Claude Code 實戰指南:從零開始到高效開發 本文整合 Anthropic 官方 Best Practices 與社群實戰 Tips,帶你由淺入深掌握 Claude Code。 什麼是 Claude Code?為什麼值得學? 如果你還在用「複製程式碼貼到 ChatGPT,再複製答案貼回去」的工作流程,Claude Code 會讓你大開眼界。 Claude Code 是 Anthropic 推出的命令列工具,它直接活在你的 terminal 裡,能夠讀懂你的整個 codeb...

Feb 18, 20265 min read31
工程師的 Claude Code 實戰指南:從零開始到高效開發

工程師的 Claude Code 實戰指南:從零開始到高效開發

工程師的 Claude Code 實戰指南:從零開始到高效開發 本文整合 Anthropic 官方 Best Practices 與社群實戰 Tips,帶你由淺入深掌握 Claude Code。 什麼是 Claude Code?為什麼值得學? 如果你還在用「複製程式碼貼到 ChatGPT,再複製答案貼回去」的工作流程,Claude Code 會讓你大開眼界。 Claude Code 是 Anthropic 推出的命令列工具,它直接活在你的 terminal 裡,能夠讀懂你的整個 codeb...

Feb 18, 20265 min read26
工程師的 Claude Code 實戰指南:從零開始到高效開發

剖析 OTel Collector Delta To Cumulative Processor

這篇筆記主要記錄我在研究 OpenTelemetry Collector Contrib 中 deltatocumulative Processor 的心得。除了基本的配置,我們直接從 Source Code 層級來看看它是怎麼運作的,特別是它在狀態管理上的設計,以及我們在生產環境踩過的那些「坑」。 1. 為什麼需要這個組件? 簡單來說,deltatocumulativeprocessor 的工作就是把 Delta (增量) 指標轉成 Cumulative (累積) 指標。 聽起來很簡單?但這是...

Jan 21, 20268 min read7
剖析 OTel Collector Delta To Cumulative Processor

Project Layout for Go

剛入門任何一門程式語言開發的人, 應該大多都是參考各路大神們的專案或者公司的專案在學習模仿。 一開始印入眼簾的應該就是 Project布局的各種長相, Project布局關心的是我們怎樣組織 Go project。 這裡針對的是資料夾跟檔案的布局,官方Blog Organizing a Go module這篇文章有給我們建議跟說明。讓我們一起讀這篇吧! 在閱讀這篇之前能先稍微理解Go Module以及Go install的用法。 官方Blog Organizing a Go module這篇文...

Oct 9, 20232 min read87
Project Layout for Go
B

BulldogBytes

8 posts