Area 1 · Claude Code 配置實務

score report 掉分:配置機制選擇 0%|review 配置 0%|測試生成 0%|iterative refinement 0%|CI flags 50%|MCP 整合 50% | 讀完 → Drill 1(快檢 10 + 情境 30)

1.1 配置機制選擇 — 先問「強制還是建議」

五個機制解五種不同問題。第一個判斷永遠是:這條規則能不能容忍被違反?

這條 guidance…
│
├─ 絕不能被違反(compliance / 金流 / 破壞性操作)?
│    ├─ 是「擋某個工具呼叫」 → settings permissions(deny/ask 規則)
│    └─ 要「檢查內容再決定/轉換」 → hooks(PreToolUse 擋、PostToolUse 轉換)
│
└─ 是建議/慣例(違反了頂多品質差)?
     ├─ 全專案、每個 session 都該知道 → CLAUDE.md
     ├─ 只有某類檔案適用          → .claude/rules/ + paths glob(省 context)
     └─ 多步驟流程、偶爾才用       → Skill(被呼叫才載入)
機制給什麼何時載入強制力
CLAUDE.md事實性/持續性規則(build 指令、慣例、架構)每 session 開始全載入(沿目錄樹往上)❌ context,「盡量遵守」
.claude/rules/ + paths只適用部分檔案的規則paths碰到 match 檔案才載入❌ 同上
Skills多步驟程序/工作流被呼叫才載入 body部分(allowed-tools 預核工具)
Hooks「不論 Claude 怎麼決定」都要執行/擋下事件觸發跑 callback✅ 真強制
settings permissions工具/檔案/網域存取控制每次工具呼叫前評估✅ 真強制,deny 最大
最大陷阱:把「寫進 CLAUDE.md(用大寫、放最前面)」當成能擋住行為的選項。官方原文:"To block an action regardless of what Claude decides, use a PreToolUse hook instead." CLAUDE.md 和 rules 是塑造行為的 context,不是 enforcement 層
補充規格:CLAUDE.md 支援 @path import——相對路徑以「含 import 的那個檔」為基準、最大遞迴 4 層、code span 內的 @ 不解析。官方建議 CLAUDE.md 保持精簡(巨檔 → 拆進 .claude/rules/ 主題檔)。
例題走一遍:測試檔慣例散落 12 個 service

:migration SQL 檔散落各 service 的 migrations/,要統一慣例、編輯到就自動套用、context 成本最低。

推理:①「自動套用」→ 刪掉 Skill(要人叫);②「慣例」不是 compliance → 不用 hook;③「只有某類檔案」→ root CLAUDE.md 常駐太貴、per-dir CLAUDE.md 要同步 12 份;→ .claude/rules/ + paths: ["**/migrations/*.sql"],match 才載入。

但若題目改一個字:「migration 檔絕不允許出現 DROP TABLE」→ 這是 must-never → PreToolUse hook 檢查 Edit/Write 內容,rules 檔擋不住。

延伸(不考):表上五個機制都在「工具呼叫前判斷指令字串」這一層,所以看不進 npm run cleanpython x.py 裡面做了什麼。OS 層還有第六層 sandbox 補這個洞 → 見 1.5 末的延伸框

1.2 Permission modes — 六個,背行為差異

Mode不問就能做典型場景
default(別名 manual)只有讀一般互動
acceptEdits讀 + 檔案編輯 + 常見檔案系統指令(mkdir/touch/rm/mv/cp/sed),限 working dir / additionalDirectories 內信任的編輯流程
plan只有讀(唯讀探索)改動前探索、出計畫等核准
auto全部,但有背景 classifier 安全檢查需新模型;Team/Enterprise 由 Owner 開啟(個人 plan 不用)
dontAsk只有 allow 清單內 + 內建唯讀指令;其餘直接 deny、不會問。例外:AskUserQuestion、組織設為 ask 的 connector 工具、標了 requiresUserInteraction 的 MCP 工具——放進 allow 也照樣被 denyCI / 腳本(沒有人能回 prompt)
bypassPermissions全部(含 protected paths);但 deny 規則仍然生效。仍會 prompt 的:explicit ask 規則、組織設為 ask 的 connector 工具、requiresUserInteraction 的 MCP 工具、rm -rf circuit breaker只限隔離容器 / VM
三個必背事實:① protected paths.git.claude.bashrc.npmrc…)除 bypassPermissions 外不會被自動核准——default/acceptEdits/plan 照樣 prompt、auto 進 classifier、dontAsk 直接 deny、allow 規則也蓋不掉;唯 bypassPermissions 直接放行 protected paths(見上表),這正是它只限隔離環境的原因;② rm -rf /rm -rf ~所有 mode 含 bypass 都有 circuit-breaker prompt;③ --dangerously-skip-permissions = bypassPermissions 的旗標形式;④ bypassPermissions ≠ 無設防——settings 的 deny 規則在 bypass 下照樣擋(✓ 實測:deny Bash(rm *) + --permission-mode bypassPermissionsrm 仍被拒)。「bypass = 全部放行、什麼都擋不住」是選項裡的假敘述。
CI 題陷阱:「pipeline 沒有人回 prompt」的正解是 dontAsk(不在 allow 清單 → 自動拒絕、流程繼續可控),不是 bypassPermissions(全放行 = 把 CI runner 的一切交給 agent,官方限定隔離環境)。兩者都「不會卡住」,差在預設方向:一個 deny、一個 allow。

1.3 Settings 層級與規則評估

優先序(高 → 低)                     permission rules 例外
Managed(企業,誰都蓋不掉)           ├ 不是覆蓋,是 merge
  ↓ CLI 參數                          ├ 任一層的 deny 永遠贏
  ↓ .claude/settings.local.json(個人)├ 高層 allow 蓋不掉低層 deny
  ↓ .claude/settings.json(team 共用) └ 評估順序固定:
  ↓ ~/.claude/settings.json(user)        deny → ask → allow(先中先贏)

路徑規則的錨點 — 單斜線不是絕對路徑

先記這句反直覺的:權限規則裡的 /Users/alice/file 不是絕對路徑

單斜線開頭的路徑是半截的——Claude Code 會自動幫你補上前面那一段。補什麼,看這條規則寫在哪個檔案。所以同一行字,換個檔案放就管到不同地方:

同一行規則:Edit(/src/**)

寫在專案的 .claude/settings.json
  自動補上  /Users/you/myapp
  你寫的                    /src/**
  實際管到  /Users/you/myapp/src/**       ✅ 正是你要的

寫在 ~/.claude/settings.json
  自動補上  /Users/you/.claude
  你寫的                      /src/**
  實際管到  /Users/you/.claude/src/**     ❌ 這資料夾根本不存在
失敗是安靜的:上面那條錯的規則不會報錯、不會警告,設定檔看起來完全正常——它只是永遠匹配不到任何東西。官方警告框原文:"A pattern like /Users/alice/file isn't an absolute path. The single leading slash anchors at the settings source, not the filesystem root."
四種寫法(斜線越多,起點越靠前)
  src/**     → 目前工作目錄
  /src/**    → 半截路徑,自動補前綴(補什麼看寫在哪個檔)
  //src/**   → 完整路徑,放哪個檔都等於 /src/**
  ~/src/**   → 家目錄底下
實務規則,不用背表:全域規則只寫 //~/,永遠不用單斜線。
單斜線只在專案設定裡刻意用——.claude/settings.json 會進 git 給十個人 clone,同一行 Edit(/src/**) 要各自對到他自己那份 src/。寫死絕對路徑就只有你一台電腦能用。這就是「自動補前綴」這個機制存在的理由。
完整對照:五個 settings 來源各自補什麼前綴(考前掃一眼)
.claude/settings.json(project)  → <專案根>/path
.claude/settings.local.json       → <啟動 Claude 的 cwd>/path
~/.claude/settings.json(user)   → ~/.claude/path      ← 最容易中招
--settings <file>                 → <該檔所在目錄>/path
CLI 旗標 / /permissions           → <啟動 cwd>/path

user settings 那條為什麼特別反直覺:專案設定會往上跳一層、跳出 .claude/(檔案放在 <專案根>/.claude/,補的前綴卻是 <專案根>);user settings 不跳,就停在 ~/.claude。你以為前綴是家目錄,其實中間多一段 .claude

官方自己舉的例子:user settings 裡的 Read(/secrets/**) 擋的是 ~/.claude/secrets/**不是你專案裡那個 secrets 資料夾。

✓ 實測(同一個檔案、同一條 deny 規則,只換斜線數):Read(//<abs>/**) → 「denied by your permission settings」;Read(/<abs>/**) → 讀得到,規則沒匹配。
另一條實測:Edit(<path>) 的 deny 也會擋住 Bash 的 rm <path>(拿掉規則就刪得掉,反證確認)。官方只列了 cat/head/tail/sed沒承諾 rm——所以正式配置仍要另外寫 Bash(rm *),不要依賴未文件化的行為。

1.4 CI / Headless CLI 旗標

旗標作用考點鑰匙
-p / --print非互動,印完就退「CI 卡住等輸入」
--output-formattext / json / stream-json(只有這三個值)選項出現 markdown/xml = 假值
--json-schema最終輸出強制符合 schema「下游機器讀」和 json 搭配
--max-turns回合上限,預設無上限,到頂 exit error「防 runaway / 爆預算」成對出現
--max-budget-usd美金成本上限,到頂即停
--permission-mode六 mode 擇一CI 用 dontAsk
--allowedTools / --disallowedTools工具 pattern 清單review agent 限唯讀
--setting-sources載入哪幾層 settings(user,project,local)「不信任 fork 的 project settings」
--mcp-config + --strict-mcp-config指定且只用該 MCP 設定CI 環境鎖定 server 來源
例題走一遍:nightly review job 的完整命令長相
claude -p "Review yesterday's merged changes" \
  --output-format json --json-schema ./findings-schema.json \
  --permission-mode dontAsk \
  --allowedTools "Read" "Grep" "Glob" "Bash(git log *)" "Bash(git diff *)" \
  --max-turns 30 --max-budget-usd 5.00

每個旗標對一個考點:非互動(-p)、機器可讀(json+schema)、無人回 prompt(dontAsk)、least privilege(唯讀工具)、防 runaway(兩個上限)。考題常抽掉其中一個問「還缺什麼」。

1.5 Hooks — 位置決定能力

user prompt ──▶ [UserPromptSubmit]* ──▶ Claude 決定呼叫工具
                                            │
                              [PreToolUse]* ─┤ exit 2 = 擋下(工具不執行)
                                            ▼
                                        工具執行
                                            │
                              [PostToolUse] ─┤ 只能轉換結果/補 context
                                            ▼   (動作已發生,擋不了)
                                     結果進 agent context
Claude 想結束 ──▶ [Stop / SubagentStop]* ── exit 2 = 不准停、繼續做
context 快滿 ──▶ [PreCompact]*            (* = 可 block 的事件)
機制陳述為假的固定款:「用 PostToolUse hook 阻止危險指令執行」——PostToolUse 跑的時候動作已經發生,不可能 block。要擋 → PreToolUse。反過來「格式正規化」放 PreToolUse 也錯位:結果還不存在,要 PostToolUse。
延伸(不考)· Sandbox — OS 層的第六層,唯一擋得住子行程的

不在 Module 1 考綱,備考不用背;但不知道它,實務判斷會出錯,所以放這裡。

前五個機制(含 hooks)都在指令送出去之前判斷,手上的材料只有指令字串。所以下面這三行對它們全部無害:

npm run clean      # package.json 裡是 "clean": "rimraf dist"
make clean         # Makefile 裡是 rm -rf build/
python cleanup.py  # 第 40 行 os.remove("Atlas/Dots/x.md")

字串裡沒有 rm、沒有目標路徑 → deny 規則和 hook 全部放行,而且它們沒出錯,只是看不到。Sandbox 攔在另一個位置:指令已經在跑,作業系統在它真的去碰檔案的那一刻拒絕(macOS Seatbelt/Linux 與 WSL2 用 bubblewrap)。限制掛在行程上、而且子行程繼承,所以 python/npm/make 叫出來的一切都在同一圈裡。

階段一:Claude Code 決定要不要送出這個指令  ← deny 規則 / hook(看字串,懂語意)
階段二:OS 執行這個行程                     ← sandbox(看實際碰的檔案,不懂語意)
看得懂看不懂
階段一語意:這是 push、這是寄信、這是刪檔包裝過的(bash deploy.sh 內部)
階段二事實:這個行程正在寫這個路徑意義:這條連線是查天氣還是寄信

兩邊的漏洞不重疊,所以是疊加不是取代。只留階段二擋不住 git push --force——它不寫任何受保護檔案,OS 只看到一條到允許網域的連線。而且階段二沒有「跳出來問你」這個選項:指令跑到一半,OS 只能允許或 Operation not permitted

覆蓋範圍的洞:sandbox 只包 Bash 及其子行程。Claude 內建的 Read/Edit/Write 不開子行程,走的是權限系統,不在 sandbox 裡。所以兩層必須一起設;好消息是 sandbox.filesystem 的路徑會和 Read/Edit 的 deny 規則合併成同一個邊界
設定與代價:寫在 settings.jsonsandbox 欄位(或跑 /sandbox,會寫進 .claude/settings.local.json)。陣列 key(allowWritedenyWriteexcludedCommands跨層合併,布林 key(enabled)由高層決定。
代價是真的:預設只能寫 cwd 與 session 暫存目錄、網路預設全擋;docker 不相容、jest 要加 --no-watchman、macOS 上 Go 寫的 CLI(ghgcloudterraform)TLS 會失敗——都得進 excludedCommands。原生 Windows 不支援(要用 WSL2)。
「sandbox」一詞有兩個意思,別混:這裡講的是 sandboxed Bash tool——還在你的機器、你的真檔案,只是行程被綁上邊界;另一個意思是整個隔離環境(容器/VM/雲端 session),那是 Claude 根本碰不到你的檔案。共同點是「裡面自由、出不去」這個性質,差別在邊界畫在哪一層。

1.6 CI Review 配置與測試生成

Review 配置三件套(那條 0% 的 objective 原文就是這三件)

  1. 載入 project 標準:CI instance 是全新 context,要餵 CLAUDE.md 三類內容——專案慣例(命名/架構/測試標準)、accepted patterns(刻意設計,如 barrel files、re-export),與排除準則(哪些類別不要報,如 generated code、vendored 依賴)。三者作為每次 review 都載入的 persistent context,否則它拿通用標準審你的刻意設計,false positive 淹沒真 finding。
  2. 限制工具:review 只需要讀 → --allowedTools 只給 Read/Grep/Glob(+ 唯讀 git)。不給 Edit/Write = review agent 不可能改壞東西。
  3. 結構化輸出--output-format json + --json-schema,讓 bot 能 parse 成 PR comments / gate 條件。
術語對照:accepted patterns / 排除準則 裡那些名詞是什麼
名詞是什麼不講就會被誤報成
barrel file只做轉出的 index.tsexport * from './a'; export * from './b',讓別人寫 import { a, b } from './utils' 就好「多餘的間接層/增加 bundle 體積/循環 import 風險」——但它其實是你刻意的模組對外邊界
re-export上面那個動作本身:把別的檔案的東西原封不動再轉出一次「沒必要的包裝」
generated code工具產生的程式碼:OpenAPI client、Prisma types、protobuf一堆風格與型別 finding——但你改不了,下次重生就蓋掉
vendored 依賴把第三方套件原始碼直接複製進 repo(vendor/third_party/審別人的專案,對你零價值

寫的時候要帶「為什麼」:只寫「barrel files are accepted」不夠——要寫「barrel files are our module public API boundary, do not flag as unnecessary indirection」,模型才分得出怎樣算違規、怎樣算正常。

真正的代價不是吵:一次 PR 冒出 200 條 finding、其中 180 條來自 generated code,人看到第三頁就放棄——從此不再看 review 結果。漏一條真 finding 傷害一次;讓人放棄看 review,傷害的是往後每一次。

測試生成品質三件套

  1. 給既有測試檔當 context —— 學專案的測試風格與結構,而不是憑通用習慣生成。
  2. 定義 fixture 慣例 —— 測試資料怎麼建、放哪、怎麼共用。
  3. 給區分準則:behavioral test(測邊界行為、錯誤路徑)vs trivial assertion(assert getter 回傳值)。沒有準則,模型會大量生成後者衝覆蓋率。
術語對照:behavioral test vs trivial assertion(附程式碼)

getter = 只負責把存進去的值原樣拿出來的方法。assertion = 測試裡那行 expect(...) / assert ...
trivial assertion = 你斷言的那件事,中間沒有經過任何邏輯

// trivial —— 存 Alice、取出 Alice,中間什麼都沒發生
it('returns the name', () => {
  expect(new User('Alice').getName()).toBe('Alice')
})

// behavioral —— 每一條對應一個真的會發生的災難
it('折扣不會讓價格變負數', () =>
  expect(calc.apply({ price: 100, discount: 150 })).toBe(0))

it('API timeout 時回傳 cached 值而不是丟例外', async () =>
  await expect(client.fetchRates()).resolves.toEqual(CACHED_RATES))

it('空清單不會炸,回傳空結果', () =>
  expect(summarize([])).toEqual({ total: 0, items: [] }))

判準一句話:這條測試失敗的時候,是哪個使用者遇到什麼問題?答不出具體場景 → 不要寫。上面第一條只有在 JavaScript 的 return 壞掉時才會失敗,那不是你的 bug;它覆蓋率 +1、抓 bug 機率≈0、欄位改名還要跟著改,淨值是負的

但 getter 裡有邏輯就該測get displayName() { return this.nickname ?? `${first} ${last}`.trim() } 有分支(有沒有 nickname)、有邊界(first 是空字串會多一個空格)——這些是真的會寫錯的地方。判準還是同一句:中間有沒有「可能寫錯的判斷」。

fixture = 測試用的預備資料或預備狀態(假使用者、假 API 回應、先建好的 DB 記錄)。「定義 fixture 慣例」= 講清楚這些東西怎麼建、放哪個目錄、跨測試怎麼共用;不講,模型會在每個測試檔各造一套。

寫進 CLAUDE.md 的收尾要明講:「寧可 5 條測到真問題,不要 40 條把覆蓋率衝到 90%;覆蓋率不是本專案的驗收指標。」——不講這句,模型就會自己把覆蓋率當成唯一看得到的分數去追。

辨識訊號:「生成的測試都很淺/瑣碎」→ 給 criteria + 既有測試當範例;不是「要求更多測試」(更多垃圾)、不是換模型(無關升級)。

1.7 Iterative refinement 三機制

  1. Concrete input/output examples:具體「輸入 → 期望輸出」範例 > prose 描述(prose 被不一致解讀 → 格式漂移)。
  2. Targeted feedback on specific failures:指著具體失敗點回饋(「第 3 段表格欄位順序錯」),不是「再改好一點」。
  3. Batched issue descriptions:相關問題打包一次給(consolidated evaluation)——一條一條擠牙膏會來回震盪、每輪重新理解 context。
辨識訊號:「格式一直漂」→ examples;「回饋很模糊沒收斂」→ targeted feedback;「改了東壞了西」→ batch 相關 issues。選項出現 fine-tune / 「MUST」大寫措辭 / 換模型 = 陷阱。

討論題(40 分鐘 session 用 — 先自己想,再來挑戰我)

  1. deny Bash(git push *)(user 層)+ allow Bash(git push origin main)(project 層)+ 一個 PreToolUse hook 回 allow——最後 git push origin main 會怎樣?為什麼?
  2. 「CLAUDE.md 寫了不准刪檔,agent 還是刪了」——列出三種真正能擋住的做法,各自的適用差異?
  3. CI 上想跑 review 又怕 fork 的 PR 挾帶惡意 project settings——哪兩個旗標組合起來處理?
  4. dontAskbypassPermissions 都「不會卡 prompt」,什麼情境下選錯會出大事?
  5. 為什麼 --max-turns 預設無上限這個事實在考題裡重要?