Boom Field Lab

Runner 24/7: ให้ Claude Code ทำงานเองตอนตีสาม — คิวงาน ด่านเสร็จ และสิ่งที่เจอจาก 91 รอบจริง

โครง runner ที่หยิบงานจากคิวมาให้ Claude Code ทำทีละชิ้นโดยไม่มีคนเฝ้า ตั้งแต่ single-instance สองชั้น ไฟล์ด่าน 'เสร็จจริง' ต่อชิ้นงาน ไปจนถึงตัวเลขจริงจาก log: 91 รอบ เสร็จ 51 ติด 40 — และเหตุผลที่สุดท้ายเราปิดโรงงานคอนเทนต์ของมันเอง

โดย วรัญชัย ยิ่งคำนึง (Boom) · เผยแพร่ครั้งแรก · ตรวจทานล่าสุด

ออฟฟิศมืดยามดึก มีคอมพิวเตอร์เครื่องเดียวเปิดจออยู่ พร้อมข้อความ Runner 24/7 ให้ AI ทำงานตอนเราหลับ

Claude Code เก่งตอนนั่งคุยสดอยู่แล้ว แต่สิ่งที่เปลี่ยนวิธีทำงานจริงคือตอนที่มัน ทำงานต่อเองได้ตอนเราไม่อยู่ ระหว่างสร้าง Boom Leverage Terminal ผมมี runner ตัวหนึ่งที่รันอยู่ใน container ตลอดเวลา หยิบงานจากคิวมาทำทีละชิ้น บันทึกนี้คือโครงจริงของมัน ตัวเลขจริงจาก log และสิ่งที่เจอระหว่างทาง — รวมถึงเหตุผลที่ทำให้ผมตัดสินใจ จำกัด สิ่งที่มันทำได้

Runner คืออะไร และทำไมต้องมี?

งานหลายอย่างไม่ต้องการคนกดทีละขั้น — รัน audit ข้อมูล, ตรวจคุณภาพ, เติมงานค้างในคิว สิ่งเหล่านี้ทำซ้ำได้และอธิบายเป็นขั้นตอนได้ ถ้าต้องเปิด session มาสั่งเองทุกครั้ง สุดท้ายมันก็ไม่ได้ทำ runner คือตัวที่แปลง "อยากให้ทำ" เป็น "ทำเองโดยไม่ต้องจำ"

แก่นของมันเรียบง่าย: แยก "งานคืออะไร" (ข้อมูลในคิว) ออกจาก "ตัวรัน" (โค้ดที่วนลูป) เพิ่มงาน = เพิ่มแถว ไม่ต้องแตะโค้ด

แผนภาพวงจร runner 5 ขั้น: หยิบงานจากคิว ตรวจนโยบาย ส่งให้ Claude Code ทำ รอไฟล์ด่านเสร็จของงานนั้น แล้วบันทึกผลลง log
วงจรหนึ่งรอบของ runner · ขั้นที่เน้นคือด่านเสร็จ — runner ไม่เชื่อคำว่า 'เสร็จแล้ว' ที่ AI พิมพ์ เชื่อเฉพาะไฟล์ที่เครื่องตรวจได้

หนึ่งรอบมี 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 เขียนทับทุกรูปแบบในคำสั่งให้เป็นชื่อที่มีเลขงานก่อนส่งเสมอ คอมเมนต์ในโค้ดจริงบันทึกไว้แบบนี้:

การ์ดข้อความจริงจากคอมเมนต์ในโค้ด runner อธิบายว่า session เขียนไฟล์ชื่อผิด ด่านจึงไม่ถูกเปิดและหมดเวลาทั้งรอบ
คอมเมนต์จริงในโค้ด 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:

กราฟแท่งผลลัพธ์ของ runner: เริ่มงาน 91 รอบ เสร็จ 51 ติดด่าน 38 ล้ม 2 และเติมคิวอัตโนมัติ 77 ครั้ง
นับ event ใน log คิว (queue_events.jsonl) ตั้งแต่ 12 ก.ค. ถึง 27 ก.ย. 2026 · ไม่นับ 714 event ที่เป็นการย้ายข้อมูลเก่าเข้าระบบ
eventจำนวนความหมาย
เริ่มงาน (run)91runner ส่งงานให้ Claude Code
เสร็จ (done)51ไฟล์ด่านปรากฏ ผ่าน
ติดด่าน (blocked)38ไม่ผ่านด่าน ถูกพักไว้ให้คนดู
ล้ม (failed)2ล้มถาวร
เติมคิว (refill)77runner เติมงานเองเมื่อคิวตื้น
คืนงานค้าง (reclaim)2งานที่ค้างสถานะ "กำลังทำ" ถูกคืนเข้าคิว

51 จาก 91 คือราว 56% ที่ผ่าน อีก 42% ติดด่าน ถ้าไม่มีด่าน 40 งานนั้นจะถูกนับว่า "เสร็จ" ทั้งที่ไม่เสร็จ และเราจะไม่มีทางรู้เลย

ข้อสรุปที่ใหญ่ที่สุด: automation ที่ดีต้องรู้ว่าอะไร ห้าม ทำ

ช่วงแรก runner ผลิตคอนเทนต์ด้วย — ข่าว บทความ วันละหลายชิ้น ในเดือนสิงหาคม 2026 ผมปิดส่วนนั้นถาวร และกฎตอนนี้คือ งานที่ออกสู่สาธารณะ (เว็บ บทความ) ต้องเกิดจากคนสั่งเท่านั้น ไม่ใช่ runner หรือ cron เหตุผลตรงไปตรงมา: งานที่มีชื่อเราอยู่บนนั้นต้องมีคนรับผิดชอบทุกชิ้น

ตัวกรองนโยบายในขั้นที่ 2 จึงไม่ได้ดูแค่ชื่องาน แต่ดูว่างานนั้น "คืออะไร" เพราะเราเคยเจอว่างานเขียนบทความที่ตั้งชื่อให้ดูเหมือนงาน product ก็ผ่านตัวกรองแบบดูชื่อได้สบายๆ

สรุป

runner ที่ดีไม่ใช่ตัวที่ฉลาด แต่เป็นตัวที่ รู้ว่าเมื่อไหร่งานเสร็จจริง, ไม่มีวันรันซ้อน, และรู้ขอบเขตของตัวเอง สามอย่างนี้คือสิ่งที่ทำให้ปล่อยทิ้งไว้ข้ามคืนได้ ส่วนความเก่งของโมเดลเป็นเรื่องรอง — ตรงกับที่ผมเขียนไว้ใน ทำไมผมสร้าง Terminal ด้วย Claude Code เพราะ harness

BOOM FIELD LAB

อยากเห็นว่าเครื่องมือพวกนี้ประกอบร่างเป็น Terminal จริงได้ยังไง?

Workshop 60 นาที — ดูการทำงานจริงบนหน้าจอ พร้อม workbook ให้ลงมือตาม ไม่ใช่สไลด์ทฤษฎี