เมื่อสัปดาห์ที่แล้วเช้ามืดประมาณตี 5 ผมตื่นขึ้นมาเจอ Telegram แจ้งว่า "ลูกค้า Newton คนใหม่ signup เสร็จ — แต่ server ขึ้นไม่ได้" งงครับ เพราะลูกค้าก่อนหน้านั้นแค่ 2 ชั่วโมงผ่านปกติ ไม่ได้แก้โค้ดอะไร ไม่ได้ขยับ snapshot — แต่ provision script ของลูกค้าคนนี้ตายเฉยๆ ไม่มี error message ที่อ่านง่ายเลย
เคสแบบนี้ปกติเป็นฝันร้ายของ dev — bug ที่เกิดเฉพาะบางคน ไม่เกิดทุกคน reproduce ก็ยาก งานเดิมก็เยอะ คนเป็นเจ้าของ infra เดี๋ยวต้องนั่ง debug ครึ่งวัน
แต่คราวนี้ผมเปิด terminal ส่งงานให้ AI agent ของผม (ทิม) แล้วก็กลับไปนอนต่อ 555
เคส: ลูกค้าใหม่ขึ้นไม่ได้ ลูกค้าเก่าผ่านปกติ
Newton ของผมเวลามีลูกค้า trial ใหม่ ระบบจะไป provision server ของ Hetzner ขึ้นมาเครื่องนึงเฉพาะลูกค้าคนนั้น ลง Tim Chat + Claude Code + อะไรอีกหลายตัว เสร็จแล้วส่ง welcome email พร้อมรหัสผ่านให้ลูกค้า
ทุกขั้นตอนนี้ทำผ่าน script ตัวเดียวชื่อ provision.sh รันบน VPS หลัง snapshot บูตเสร็จ
ลูกค้าก่อนหน้านี้ 17 คน ผ่านปกติทั้งหมด — server ขึ้น login ได้ ใช้งานได้
ลูกค้าคนที่ 18 — script รันแล้วตายเงียบๆ ตอนหลัง set -e abort ก่อน .env file ถูกสร้าง → newton.service เปิดไม่ได้ → service crash-loop 1,000+ รอบ → ลูกค้าเปิดเว็บเจอหน้า login ไม่ขึ้น
ผมส่งให้ AI สืบ — บอกแค่ประโยคเดียว
ผมไม่ได้บอก AI agent ว่า bug อยู่ตรงไหน ผมไม่รู้เองด้วยซ้ำตอนนั้น
บอกแค่: "ลูกค้าใหม่ขึ้นไม่ได้ ลูกค้าเก่าผ่านปกติ ไปหาดูให้หน่อย"
AI ทำสิ่งที่ผมประทับใจมาก — มันไม่เดา ไม่ stab in the dark มันเดินไปดู log ของ 2 server ที่อยู่ใกล้กันที่สุดในเวลา
- Server 17 (ลูกค้า Anupap) — provision เวลา 03:23 UTC วันเดียวกัน → ผ่าน exit 0
- Server 18 (ลูกค้าใหม่) — provision เวลา 05:00 UTC → fail exit 9
มัน diff log ของทั้ง 2 เครื่องบรรทัดต่อบรรทัด
เจอ diff จุดเดียวที่มีนัยยะ — random password ที่ระบบ generate ให้ลูกค้าคนใหม่ ขึ้นต้นด้วยขีด -
แค่ตัวเดียวเท่านั้นเอง
ทำไมขีดตัวเดียวถึงพังทั้ง pipeline
นี่คือจุดที่ผมว่าน่าสนใจที่สุด — bug มันเล็กแต่ลึกครับ
ใน provision.sh มีบรรทัดประมาณนี้:
node -e "...some script..." "$PASSWORD"
เจตนาคือส่ง password ไปเป็น argument ตำแหน่งแรกให้ Node script เอาไป hash แล้วเขียนลง database
ปัญหา: Node CLI ก่อนจะ run script จะ parse argv ทุกตัวที่ขึ้นต้นด้วย - เป็น flag (เช่น -v, -h, --help) — ถ้า password เริ่มด้วย - อย่าง -BATDNqXyz... Node จะตีความว่ามันคือ flag ที่ไม่รู้จัก แล้วโยน node: bad option: -B ออกมา exit code 9
เพราะ provision.sh มี set -e อยู่ — exit code ไม่เป็น 0 ปุ๊บ script abort ตรงนั้นเลย ไม่มีโอกาสได้สร้าง .env ไม่มีโอกาสได้ตั้ง newton.service พอ systemd boot service ก็เปิดไฟล์ไม่เจอ → crash → restart → crash → restart → vicious loop
ทำไมเพิ่งเจอตอนนี้
คำถามที่สำคัญกว่า — bug นี้มีอยู่ตั้งแต่วันที่ผม launch Newton ทำไมเพิ่งเจอเดือนนี้
คำตอบอยู่ที่ Python secrets.token_urlsafe(16) ที่ ระบบ auto-provision ใช้ generate password
token_urlsafe ใช้ตัวอักษรทั้งหมด 64 ตัว: A-Z, a-z, 0-9, -, _
โอกาสที่ตัวแรกจะเป็น - หรือ _ = 2 / 64 = 3.125%
แปลว่า ทุกๆ ลูกค้า 30 คน จะมีคนเจอเคสนี้ประมาณ 1 คน — โดยสุ่ม
เรามีลูกค้า 17 คน ก่อนหน้านี้ทั้งหมด random ออกมาเริ่มด้วย letter หรือ digit ปกติ — เลยไม่เจอ
ลูกค้าคนที่ 18 เป็นคนแรกที่ random เริ่มด้วย - แค่นั้นเอง
เคสนี้คือเคสที่ คนเป็น dev ก็เจอยาก เพราะใน dev environment เราไม่ค่อยรัน end-to-end เป็นร้อยรอบเพื่อหา edge case 3% AI agent ที่นั่งดู production จริงเทียบ log จริงเท่านั้นที่จับเจอ
Fix จริงๆ คือ 2 ตัวอักษร
วิธีแก้ที่ AI ผมเสนอคือเติม -- separator ก่อน argument ตัว user-input
node -e "...some script..." -- "$PASSWORD"
UNIX convention: -- เป็นสัญญาณบอกว่า "หยุด parse flag ตรงนี้ ที่เหลือถือเป็น positional argument ทั้งหมด"
เพิ่ม 2 ตัวอักษรนี้ลงไป — Node ไม่ตีความ password เป็น flag อีก รหัสรอดเข้าไปเป็น value ปกติ
ผมรัน reproduce ก่อน fix:
node -e '...' '-BATDNqXyz...'→ exit 9 ❌node -e '...' -- '-BATDNqXyz...'→ exit 0 ✅
Reproduce ผ่าน — fix แน่ใจว่าใช้ได้
AI ไม่หยุดแค่ fix bug
ตรงนี้คือเหตุผลที่ผมรัก AI agent ที่ทำงานบน server ตัวเองครับ — มันไม่หยุดแค่ "patch ให้ปัญหาหายไป" แต่ตั้งคำถามต่อว่า "ถ้าเกิดอีกแบบอื่นเราจะเตรียมตัวยังไง"
นอกจาก fix ตัว -- AI ผมเพิ่มเกราะกัน silent failure อีกหลายชั้น:
- Master provision.sh ที่
/opt/newton/setup/— เดิม script ฝังอยู่ใน Hetzner snapshot → แก้ทีต้อง rebuild snapshot 3 region ตอนนี้ scp ลง VPS ทุก provision → fix ใหม่ใช้ผลกับ provision รอบถัดไปทันที - EXIT trap ที่พิมพ์บรรทัดที่ตาย — ถ้า script ตายอีก จะมี
LINENO + BASH_COMMANDของบรรทัดที่ทำให้ตายโผล่ใน log ทันที ไม่ต้องเดาอีก - Sanity check
.env+systemctl is-active— ก่อน script return 0 ต้องเช็คว่า .env ถูกเขียนจริง และ newton.service active จริง ถ้า fail ทั้ง 2 ตัวก็ exit ด้วย error code ที่ชัดเจน - Retry 1 รอบที่ 30s sleep — เผื่อ network glitch ชั่วคราว
- Log full stdout/stderr — ลง
/var/log/newton/provision-{server_id}-{ts}.logทุกครั้ง ไม่มี> /dev/nullอีก - ถ้า fail หลัง retry → status='error' + ไม่ส่ง welcome email — เดิม main.py ส่ง welcome email แม้ provision fail → ลูกค้าได้รหัสที่ใช้ไม่ได้ → แย่กว่าไม่ได้รับเมล แก้ให้ถ้า fail จริงก็ไม่ส่ง
- Telegram alert ผมทันที — ถ้าเจอ provision fail ครั้งหน้า ผมรู้ภายในวินาทีเดียว
รวมเวลาตั้งแต่ผมตื่นขึ้นมาเจอ alert จนถึงลูกค้าได้ server พร้อมใช้ + เกราะป้องกันเคสซ้ำขึ้น production เรียบร้อย — ไม่ถึง 1 ชั่วโมง
บทเรียน — สำหรับ dev และเจ้าของธุรกิจ
เคสนี้ผมเขียนเป็นบล็อกเพราะมันมีบทเรียน 2 ชั้น
ชั้นแรก สำหรับ dev: random secret ที่ใช้ใน CLI argument ต้องระวัง flag-parsing — -- separator คือทางออกที่สะอาดที่สุด ไม่งั้น base64 / urlsafe encoding ที่ดูเหมือนปลอดภัยจะกลายเป็น bug 3% ที่หลบในโปรดักชันได้นาน
ชั้นที่สอง สำหรับเจ้าของธุรกิจที่ไม่ใช่ dev: bug แบบนี้ถ้าไม่มี AI agent คอย monitor + debug 24/7 มันจะกลายเป็นลูกค้าหายเงียบๆ — คนที่ signup แล้วเปิด server ไม่ได้ ไม่กลับมาแน่ๆ ครับ ส่วนใหญ่ไม่ส่ง support ticket ด้วยซ้ำ ขอเงินคืนแล้วไปใช้คู่แข่ง
ระบบที่มี AI agent ติดตัวเอง 24 ชั่วโมง รู้ทุกอย่าง ทำได้ทุกอย่าง — แก้ bug 3% นี้ภายในชั่วโมงเดียวก่อนลูกค้าจะเปิดมาดูซ้ำ คือความแตกต่างระหว่าง เซิร์ฟเวอร์ส่วนตัวกับ shared platform
เรื่องนี้คือเหตุผลที่ Newton เป็น managed
คนถามผมเยอะมากว่า Newton ต่างจาก ChatGPT หรือ AI chatbot อื่นตรงไหน ทำไมต้องใช้ AI agent บนเซิร์ฟเวอร์ส่วนตัว แทนที่จะใช้ของฟรี
คำตอบคือเคสแบบนี้แหละ — bug ที่เกิด 3% ของเวลา ที่ engineer เป็นคนต้องนั่งงัด log เปรียบเทียบ ที่ลูกค้าทั่วไปไม่มีทางเจอเอง — มันต้องการคนคอยดูทุกวินาที
Newton ทำให้ลูกค้าได้ AI agent ส่วนตัว ทำงานบน server ของตัวเอง พร้อม managed service ที่ผมกับทีมคอย push fix ให้ทุกเครื่องพร้อมกัน เวลาเจอ bug แบบนี้
เจ้าของธุรกิจที่ไม่ใช่ dev ไม่ต้อง debug Node CLI ไม่ต้องไล่ log ไม่ต้องตื่นมาดู server ตอนตี 3 — แค่รัน AI agent ทำงานให้ ส่วนเรื่อง infra เป็นหน้าที่ของเรา
หลังจากปะ bug นี้ AI ผมไม่หยุดแค่นั้น มันไปไล่ flow signup ทั้งหมดต่อ แล้ว เจอฟิลด์ password บนฟอร์มที่เปิดมาเดือนนึงแต่ไม่เคยทำงาน — เสนอให้ลบทิ้งแทนที่จะ fix อีกเคสที่ดี ลองอ่านดูครับ
คำถามที่พบบ่อย
-- separator ใน UNIX command line คืออะไร และควรใช้เมื่อไหร่
-- คือสัญญาณบอก program ว่า "หยุด parse flag ตรงนี้ ที่เหลือทั้งหมดถือเป็น positional argument" ครับ ควรใช้ทุกครั้งที่ส่ง user input (password, filename, ข้อความจากผู้ใช้) เป็น argument ให้ command เพราะ user อาจพิมพ์ข้อความที่เริ่มด้วย - ซึ่ง program อาจตีความผิดเป็น flag ได้
set -e ใน shell script คืออะไร มีข้อดีข้อเสียยังไง
set -e สั่งให้ script หยุดทันทีเมื่อมี command ใดก็ตาม return exit code ที่ไม่ใช่ 0 ครับ ข้อดีคือป้องกันไม่ให้ script รันต่อหลังจากเกิด error โดยเงียบๆ ข้อเสียคือถ้าไม่มี error handling ที่ดี script จะ abort แบบไม่มี log ชัดเจน วิธีแก้คือเพิ่ม EXIT trap ที่พิมพ์บรรทัดและคำสั่งที่ทำให้ตายออกมาใน log ทุกครั้ง
bug แบบ probabilistic ที่เกิดแค่บางครั้ง ป้องกันหรือ detect ได้ยังไง
วิธีที่ดีที่สุดคือ log ทุกอย่างให้ครบครับ อย่า redirect output ทิ้งด้วย /dev/null เพราะจะทำให้หา root cause ไม่เจอเวลา bug โผล่ นอกจากนั้นควรมี sanity check ก่อน script return success เพื่อยืนยันว่าทุกอย่างที่ควรสร้างถูกสร้างจริง และมีระบบ alert (เช่น Telegram) แจ้งทันทีเมื่อเกิด failure จะได้แก้ได้ก่อนที่ลูกค้าจะมาแจ้ง
ทำไม secrets.token_urlsafe ของ Python ถึงมีโอกาสได้ - หรือ _ เป็นตัวแรก
token_urlsafe ใช้ตัวอักษร 64 ตัวจาก Base64 URL-safe charset: A-Z, a-z, 0-9, - และ _ ครับ โอกาสที่ตัวแรกจะเป็น - หรือ _ คือ 2/64 ≈ 3% ซึ่งดูน้อยแต่ถ้ามีลูกค้า 30-50 คนก็ต้องเจอแน่นอน วิธีแก้คือไม่ควรส่ง secret ที่ generate มาให้ CLI โดยตรงโดยไม่มี -- separator หรือจะใช้ prefix ตายตัวให้ password ขึ้นต้นด้วยตัวอักษรเสมอก็ได้
ถ้าคุณอยากมี AI agent ส่วนตัวที่ทำงานเหมือน senior dev แบบนี้ — แกะ bug ระดับลึก เขียน fix และ deploy ให้เอง ลอง Newton ได้เลยครับ เซิร์ฟเวอร์ส่วนตัว + AI agent พร้อมใช้ภายใน 10 นาที พร้อม managed protection ตลอดเวลา
— ปอนด์
