Runner 24/7: ให้ Claude Code ทำงานเองตอนตีสาม — คิวงาน ด่านเสร็จ และสิ่งที่เจอจาก 91 รอบจริง
โครง runner ที่หยิบงานจากคิวมาให้ Claude Code ทำทีละชิ้นโดยไม่มีคนเฝ้า ตั้งแต่ single-instance สองชั้น ไฟล์ด่าน 'เสร็จจริง' ต่อชิ้นงาน ไปจนถึงตัวเลขจริงจาก log: 91 รอบ เสร็จ 51 ติด 40 — และเหตุผลที่สุดท้ายเราปิดโรงงานคอนเทนต์ของมันเอง
โดย วรัญชัย ยิ่งคำนึง (Boom) · เผยแพร่ครั้งแรก · ตรวจทานล่าสุด
Claude Code เก่งตอนนั่งคุยสดอยู่แล้ว แต่สิ่งที่เปลี่ยนวิธีทำงานจริงคือตอนที่มัน ทำงานต่อเองได้ตอนเราไม่อยู่ ระหว่างสร้าง Boom Leverage Terminal ผมมี runner ตัวหนึ่งที่รันอยู่ใน container ตลอดเวลา หยิบงานจากคิวมาทำทีละชิ้น บันทึกนี้คือโครงจริงของมัน ตัวเลขจริงจาก log และสิ่งที่เจอระหว่างทาง — รวมถึงเหตุผลที่ทำให้ผมตัดสินใจ จำกัด สิ่งที่มันทำได้
Runner คืออะไร และทำไมต้องมี?
งานหลายอย่างไม่ต้องการคนกดทีละขั้น — รัน audit ข้อมูล, ตรวจคุณภาพ, เติมงานค้างในคิว สิ่งเหล่านี้ทำซ้ำได้และอธิบายเป็นขั้นตอนได้ ถ้าต้องเปิด session มาสั่งเองทุกครั้ง สุดท้ายมันก็ไม่ได้ทำ runner คือตัวที่แปลง "อยากให้ทำ" เป็น "ทำเองโดยไม่ต้องจำ"
แก่นของมันเรียบง่าย: แยก "งานคืออะไร" (ข้อมูลในคิว) ออกจาก "ตัวรัน" (โค้ดที่วนลูป) เพิ่มงาน = เพิ่มแถว ไม่ต้องแตะโค้ด

หนึ่งรอบมี 5 ขั้น:
| ขั้น | ทำอะไร | กันปัญหาอะไร |
|---|---|---|
| 1. หยิบงาน | อ่านแถวถัดไปจากคิว | — |
| 2. ตรวจนโยบาย | งานต้องอยู่ในขอบเขตที่อนุญาต ไม่งั้นทิ้ง | runner ไปทำงานที่ไม่ควรทำ |
| 3. ส่งให้ Claude Code | เปิด session ทำงานจนจบ | — |
| 4. รอไฟล์ด่าน | รอไฟล์สัญญาณที่ ผูกกับเลขงานนั้น ภายในเพดานเวลา | ประกาศว่าเสร็จทั้งที่ยังไม่เสร็จ |
| 5. บันทึกผล | เขียน event ลง log แบบต่อท้ายเท่านั้น | ย้อนดูไม่ได้ว่าเกิดอะไรขึ้น |
ทำไม "มีตัวเดียว" ต้องกันถึงสองชั้น?
เพราะ runner ผีตัวที่สองคือหายนะเงียบ สองตัวหยิบงานเดียวกัน เขียนไฟล์เดียวกัน ผลลัพธ์เพี้ยนโดยไม่มี error สักบรรทัด ผมเคยเจอตอนย้ายโฟลเดอร์แล้วมีตัวเก่าค้างรันซ้อน
วิธีที่ใช้อยู่ตอนนี้กันสองชั้น:
- ชั้นที่ 1 — ตัวจัดการ container บังคับให้มีสำเนาเดียว และตั้งให้ restart เองเมื่อล้ม
- ชั้นที่ 2 — ล็อกกลางแบบมีอายุ runner ต้องจองล็อกก่อนเริ่ม (อายุ 60 วินาที ต่ออายุทุก 20 วินาที) ถ้ามีคนถือล็อกอยู่แล้ว ตัวที่สองถูกปฏิเสธทันทีโดยไม่เริ่มทำงาน และถ้าล็อกหลุดกลางทาง runner หยุดตัวเองทันที
ทำไมไม่ใช้ file lock ธรรมดา? เพราะ file lock ข้ามขอบเขต container กับโฟลเดอร์ที่แชร์กันไม่ได้ — มันกันได้แค่ในเครื่องเดียว ซึ่งไม่ใช่ปัญหาที่เราเจอ
ทำไม "เสร็จ" ต้องเป็นไฟล์ ไม่ใช่คำพูดของ AI?
เพราะ AI ที่พิมพ์ว่า "เสร็จแล้ว" กับงานที่เสร็จจริงคือคนละเรื่อง ทุกงานในคิวจึงมีบล็อก DONE ที่สั่งให้สร้างไฟล์สัญญาณ แล้ว runner เฝ้าดูไฟล์นั้น
เรื่องที่เจ็บที่สุดคือ ชื่อไฟล์ต้องผูกกับเลขงาน ช่วงแรกทุกงานใช้ชื่อไฟล์เดียวกัน แล้วเกิดเคสที่ session หนึ่งเขียนไฟล์ผิดชื่อ ด่านไม่ถูกเปิด runner รอจนหมดเพดานเวลาทั้งที่งานเสร็จแล้ว ตอนนี้ runner เขียนทับทุกรูปแบบในคำสั่งให้เป็นชื่อที่มีเลขงานก่อนส่งเสมอ คอมเมนต์ในโค้ดจริงบันทึกไว้แบบนี้:

'SESSION_COMPLETED.txt' but the runner only watches SESSION_COMPLETED_{id}.txt → the session writes the wrong file → gate never triggers → full-ceiling timeout.
อีกกรณีที่ด่านช่วยวินิจฉัยได้คือ โควตาหมด — ถ้าหลายงานติดกันหมดเวลาโดยไม่มีไฟล์ด่านเลย รูปแบบนั้นแทบจะแปลว่าบัญชีชนเพดานการใช้งาน ไม่ใช่งานยาก runner จึงนับสถิติ "หมดเวลาติดกัน" แล้วแจ้งเตือนแทนที่จะวนต่อไปเงียบๆ
ตัวเลขจริงจาก log บอกอะไร?
log คิวเป็นแบบต่อท้ายอย่างเดียว (แก้ย้อนไม่ได้) นับตั้งแต่ย้ายมาใช้ระบบนี้วันที่ 12 ก.ค. 2026 ถึง 27 ก.ย. 2026:

| event | จำนวน | ความหมาย |
|---|---|---|
| เริ่มงาน (run) | 91 | runner ส่งงานให้ Claude Code |
| เสร็จ (done) | 51 | ไฟล์ด่านปรากฏ ผ่าน |
| ติดด่าน (blocked) | 38 | ไม่ผ่านด่าน ถูกพักไว้ให้คนดู |
| ล้ม (failed) | 2 | ล้มถาวร |
| เติมคิว (refill) | 77 | runner เติมงานเองเมื่อคิวตื้น |
| คืนงานค้าง (reclaim) | 2 | งานที่ค้างสถานะ "กำลังทำ" ถูกคืนเข้าคิว |
51 จาก 91 คือราว 56% ที่ผ่าน อีก 42% ติดด่าน ถ้าไม่มีด่าน 40 งานนั้นจะถูกนับว่า "เสร็จ" ทั้งที่ไม่เสร็จ และเราจะไม่มีทางรู้เลย
ข้อสรุปที่ใหญ่ที่สุด: automation ที่ดีต้องรู้ว่าอะไร ห้าม ทำ
ช่วงแรก runner ผลิตคอนเทนต์ด้วย — ข่าว บทความ วันละหลายชิ้น ในเดือนสิงหาคม 2026 ผมปิดส่วนนั้นถาวร และกฎตอนนี้คือ งานที่ออกสู่สาธารณะ (เว็บ บทความ) ต้องเกิดจากคนสั่งเท่านั้น ไม่ใช่ runner หรือ cron เหตุผลตรงไปตรงมา: งานที่มีชื่อเราอยู่บนนั้นต้องมีคนรับผิดชอบทุกชิ้น
ตัวกรองนโยบายในขั้นที่ 2 จึงไม่ได้ดูแค่ชื่องาน แต่ดูว่างานนั้น "คืออะไร" เพราะเราเคยเจอว่างานเขียนบทความที่ตั้งชื่อให้ดูเหมือนงาน product ก็ผ่านตัวกรองแบบดูชื่อได้สบายๆ
สรุป
runner ที่ดีไม่ใช่ตัวที่ฉลาด แต่เป็นตัวที่ รู้ว่าเมื่อไหร่งานเสร็จจริง, ไม่มีวันรันซ้อน, และรู้ขอบเขตของตัวเอง สามอย่างนี้คือสิ่งที่ทำให้ปล่อยทิ้งไว้ข้ามคืนได้ ส่วนความเก่งของโมเดลเป็นเรื่องรอง — ตรงกับที่ผมเขียนไว้ใน ทำไมผมสร้าง Terminal ด้วย Claude Code เพราะ harness