เขียนใน CLAUDE.md แล้วมันไม่ทำตาม — ไม่ใช่เขียนไม่ดี แต่เขียนผิดที่
CLAUDE.md เป็น context ไม่ใช่ configuration มันเลยไม่ใช่กฎ ของที่ห้ามเด็ดขาดใช้ hook กฎเฉพาะบางไฟล์ไปอยู่ใน .claude/rules/ กับ paths แล้วให้ CLAUDE.md เหลือแต่ของที่ต้องรู้ทุกงาน
ผมเขียนไว้ใน CLAUDE.md ว่าอย่าอ่าน .env
มันอ่าน
เขียนใหม่ ตัวหนา ใส่คำว่าห้าม ขึ้นบรรทัดแรกของไฟล์เลย มันก็ยังอ่าน
เรื่อง push เองก็เหมือนกัน บอกว่าอย่า push เอง ให้ถามก่อน สองวันต่อมาก็เห็น commit ขึ้นไปเรียบร้อย
ตอนนั้นคิดว่าเขียนไม่ชัดพอ ก็เลยไปเขียนใหม่ให้ชัดขึ้น วนอยู่แบบนี้อยู่พักใหญ่
ที่จริงไม่ใช่เขียนไม่ชัด
ใน doc ของ Anthropic เอง หน้าที่ชื่อ How Claude remembers your project เขียนไว้ตรงๆ แบบนี้
"Claude treats them as context, not enforced configuration."
อ่านอีกรอบนะครับ context ไม่ใช่ configuration
แปลว่าทุกอย่างที่เราเขียนใน CLAUDE.md มันไม่ได้ "สั่ง" อะไรเลย มันแค่ถูกโยนเข้าไปในหน้าต่างความจำของ session นั้น ไปนั่งรวมกับ prompt ที่เราพิมพ์ กับโค้ดที่มันเพิ่งอ่าน กับผลลัพธ์ของ tool ที่มันเพิ่งรัน แล้วก็ลุ้นกันว่ารอบนี้มันจะหยิบอันไหนมาใช้
มันเป็นคำขอ ไม่ใช่กฎ
พอเข้าใจตรงนี้ เรื่องที่เคยงงก็หายงงหมด — ที่เขียนตัวใหญ่ขึ้นแล้วไม่ช่วย เพราะมันไม่เคยแพ้เรื่องเสียงดัง มันแพ้เรื่องมันไม่ใช่กฎตั้งแต่แรก
แล้วทำไมไม่มีใครรู้
เพราะ Claude Code โชว์ให้เห็นไฟล์เดียว
เปิดมาปุ๊บ ทุกคนเจอ CLAUDE.md ก็เลยยัดทุกอย่างลงไปในนั้น กฎความปลอดภัย กฎ naming กฎ test กฎ commit message กฎห้ามแตะ production อยู่ปนกันหมดในไฟล์เดียว
แต่จริงๆ มันมีสามที่ให้เขียน แล้วสองที่ที่เหลืออยู่ใน doc ที่ไม่มีใครเปิด
ที่แย่กว่านั้นคือ สองที่ที่เหลือนั่นแหละที่ทำงานจริง
ที่หนึ่ง — ของที่ห้ามเด็ดขาด ต้องใช้ hook
ถ้าเป็นเรื่องที่ยอมให้เกิดไม่ได้เลยสักครั้ง เขียนใน CLAUDE.md ไม่พอ ต้องใช้ hook
hook ไม่ใช่ข้อความ มันคือโค้ดที่รันก่อนที่ agent จะเรียก tool ทุกครั้ง ถ้าเงื่อนไขเข้า มันตอบกลับว่าไม่อนุญาต แล้วจบตรงนั้น ไม่ต้องรอให้ model ตัดสินใจ ไม่ต้องลุ้นว่ารอบนี้จะจำได้มั้ย
ใส่ใน .claude/settings.json
{
"hooks": {
"PreToolUse": [{
"matcher": "Read|Edit|Write|Bash",
"hooks": [{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/guard.sh"
}]
}]
}
}
แล้วสคริปต์ก็ไม่มีอะไรเลย อ่าน JSON ที่ส่งเข้ามาทาง stdin ดูว่าจะทำอะไรกับไฟล์ไหน ถ้าไม่ชอบก็ตอบ deny กลับไป
#!/usr/bin/env bash
input=$(cat)
if echo "$input" | grep -qE '\.env|credentials|id_rsa'; then
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"secret file"}}'
exit 0
fi
if echo "$input" | grep -qE 'push --force|push -f|rm -rf /'; then
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"destructive"}}'
exit 0
fi
exit 0
chmod +x แล้วก็จบ
ต่างกันตรงนี้ครับ — CLAUDE.md บอกว่าอย่าทำ hook ทำให้ทำไม่ได้
อันแรกเป็นความหวัง อันหลังเป็นโค้ด
ของที่ควรอยู่ตรงนี้มีไม่เยอะ ประมาณสาม-สี่ข้อพอ ไฟล์ secret, force push, ลบทั้งโฟลเดอร์, แตะ production config นอกนั้นไม่ต้อง เดี๋ยวจะกลายเป็นทำงานไม่ได้
ที่สอง — กฎเฉพาะบางไฟล์ ไปอยู่ใน .claude/rules/
อีกอาการที่เจอตอนไปรื้อไฟล์ตัวเอง
ทุกครั้งที่เปิด session ไฟล์ยาวๆ นั้นถูกโหลดทั้งก้อน ผมลองวัดดูเล่นๆ สั่งให้มันตอบแค่คำว่า ok คำเดียว prompt สิบ token แต่ของที่โหลดไปก่อนหน้านั้น สามหมื่นสี่พัน token
แล้วพอไฟล์ยาวขึ้นเรื่อยๆ มันเริ่มไม่สนใจ ไม่ใช่เฉพาะส่วนที่ไม่เกี่ยว — ทั้งไฟล์ รวมส่วนที่สำคัญด้วย
ช่วงนี้มีเทคนิคนึงแชร์กันเยอะ คือครอบกฎด้วยแท็ก <important if="..."> เพื่อให้กฎบางข้อดังขึ้นเฉพาะตอนที่เกี่ยว
ลองแล้วครับ ไฟล์ ใหญ่ขึ้น 2KB
เพราะของทุกอย่างยังอยู่ใน context เหมือนเดิม แค่ถูกถ่วงน้ำหนักใหม่ แท็กที่ครอบเข้าไปก็กินที่เพิ่มอีก แล้วเทคนิคนี้ก็ไม่ได้อยู่ใน doc ของ Anthropic ด้วย มันมาจาก blog ที่แชร์ต่อๆ กัน
ของที่อยู่ใน doc จริงๆ คือโฟลเดอร์ .claude/rules/
.claude/
├── CLAUDE.md ← เหลือแต่ของที่ต้องรู้ทุกครั้ง
└── rules/
├── api.md
├── frontend.md
├── testing.md
└── security.md
แต่ละไฟล์ใส่ frontmatter บอกไปเลยว่ากฎชุดนี้ใช้กับไฟล์ไหน
---
paths:
- "src/api/**/*.ts"
---
# กฎของฝั่ง API
- ทุก endpoint ต้อง validate input ก่อนเสมอ
- error response ใช้ format กลางตัวเดียว
- เขียน OpenAPI comment กำกับทุกตัว
จุดที่ต่างจากเทคนิคแท็กคือ ไม่ได้ให้น้ำหนักน้อยลง แต่ไม่โหลดเลย
กฎ API จะไม่อยู่ใน context ตอนที่กำลังแก้ CSS กฎ test จะไม่อยู่ตอนเขียน migration มันจะโผล่มาตอนที่ Claude ไปแตะไฟล์ที่ตรง pattern เท่านั้น
ใส่หลาย pattern ได้ ใช้ปีกกาได้ด้วย
---
paths:
- "src/**/*.{ts,tsx}"
- "tests/**/*.test.ts"
---
ถ้าไม่ใส่ paths เลย ก็โหลดตลอดเหมือน CLAUDE.md ปกติ เอาไว้ใช้กับกฎที่จริงๆ แล้วต้องรู้ทุกงาน
ไฟล์ในโฟลเดอร์นี้ถูกอ่านแบบ recursive ด้วย เลยแบ่งเป็น rules/frontend/ rules/backend/ ได้ถ้าโปรเจกต์ใหญ่
แล้วอะไรควรอยู่ตรงไหน
ห้ามเด็ดขาด ยอมให้พลาดไม่ได้ → hook
กฎที่ใช้เฉพาะบางไฟล์ → .claude/rules/ + paths
กฎที่ต้องรู้ทุกงานจริงๆ → CLAUDE.md (สั้นๆ)
เกณฑ์ง่ายๆ ที่ใช้ได้เลย
ถามตัวเองว่า ถ้ามันพลาดข้อนี้ครั้งเดียว เสียหายแค่ไหน
ถ้าคำตอบคือ secret หลุด หรือ production พัง — อันนั้นไม่ควรเป็นข้อความในไฟล์ ควรเป็น hook
ถ้าคำตอบคือ style ไม่ตรง เดี๋ยวก็แก้ได้ — อันนั้นอยู่ใน rules หรือ CLAUDE.md ก็พอ
สรุป
CLAUDE.md ไม่ได้พัง มันแค่ไม่ได้ถูกออกแบบมาให้เป็นกฎ เราต่างหากที่เอาทุกอย่างไปกองไว้ที่เดียว เพราะมันเป็นที่เดียวที่เขาโชว์ให้เห็นตอนเริ่ม
คืนนี้ทำสองอย่างพอครับ
หนึ่ง — เขียน hook ตัวแรก กัน .env ไว้ก่อน ห้าบรรทัดจบ
สอง — เปิด CLAUDE.md ขึ้นมา หากฎที่ใช้เฉพาะบางโฟลเดอร์สักข้อ ย้ายออกไปไว้ใน .claude/rules/ แล้วใส่ paths กำกับ
แค่นี้ไฟล์ก็สั้นลง แล้วของที่สำคัญจริงๆ ก็เลิกเป็นแค่คำขอ
มันมีสามที่ให้เขียนมาตั้งนานแล้ว แค่ที่แรกเป็นที่เดียวที่เขาโชว์ให้เห็น
เอาไปลองได้เลยครับ