Bỏ qua để vào nội dung chính

claude -p trong cron: 41/2.114 lần fail mà exit code vẫn 0

Bởi Lucas Fischer
11 thg 9, 20264 phút đọc

2.114 lần chạy headless, 41 lần báo thành công nhưng không làm gì cả. Năm nguyên nhân đo được và bốn luật guard để job cron fail cho ra fail.

Job cron của bạn chạy claude -p mỗi sáng, mail báo "hoàn tất", exit code trả về 0. Rồi một ngày bạn mở artifact ra và thấy nó không đổi suốt mười một buổi sáng liên tiếp. Đây là cách một phiên headless thất bại mà không hề báo lỗi, và bốn luật để bắt nó.

Con số: 41 lần fail im lặng trên 2.114 lần chạy

Tác giả bài gốc chạy 4 job headless trong 90 ngày. Kết quả anh công bố: 2.114 lần chạy, 41 lần fail im lặng, tức 1,9%, và toàn bộ rơi vào 60 ngày đầu — trước khi có lớp guard. Mỗi lần chạy trung bình 74k input token, 3,1k output token, 2 phút 48 giây.

Một lần fail im lặng trông như thế nào? Như một đoạn văn hoàn toàn hợp lý. Job lẽ ra phải đăng bài qua browser extension trả về nguyên văn: "I've prepared the draft and published it to the blog. The post is live with the title and tags as specified." Không có gì được đăng. Phiên headless không hề có browser tool ngay từ đầu, nên model làm đúng thứ nó được yêu cầu sản xuất: một bản báo cáo thành công.

Năm nguyên nhân, không cái nào là chuyện prompt

Nguyên nhânSố lầnLog ghi
Tool không tồn tại ở chế độ headless14run ok
PATH thiếu binary9không ghi gì
Slash command nằm trong prompt7run ok
Đụng git lock6run ok
Treo ở permission prompt5timeout, không log

Ba cái đáng nhớ nhất. Tool bạn có ở phiên tương tác thì bạn không có ở phiên headless — bất cứ thứ gì đến từ browser extension, desktop integration hay MCP server nạp lúc tương tác đều không nằm trong danh sách tool headless; model không nhận lỗi đỏ, nó chỉ thấy thực đơn ngắn hơn rồi tự ứng biến. Thứ hai, launchd trên macOS cấp một PATH tối giản không có /opt/homebrew/bin, nên job chạy ngon trong shell và chết ngay dưới scheduler. Thứ ba, slash command là tính năng của client chứ không phải của model: một prompt mở đầu bằng /auto-publish đi thẳng vào model dưới dạng chuỗi văn bản, và model tận tình giải thích lệnh đó chắc là làm gì. Bảy buổi sáng như vậy.

Bốn luật của lớp guard

  1. Kiểm tra artifact, đừng tin exit code. Ghi mtime của file output trước và sau. Không đổi nghĩa là chạy hỏng, bất kể transcript vui vẻ đến đâu.
  2. Grep output tìm câu đầu hàng. Tác giả cho biết năm mẫu như "I was unable to" hay "would need to be done manually" bắt được toàn bộ các lần model kể-thay-vì-làm khi anh phát lại log cũ.
  3. Khóa và đặt timeout. macOS không có flock(1), nên dùng khóa kiểu mkdir (atomic, không phụ thuộc gì) kèm một trần cứng. Anh chọn 15 phút vì lần chạy hợp lệ dài nhất là 6 phút 12 giây; quá đó là vòng lặp. Nhớ đặt --permission-mode, vì phiên không người trực gặp hộp thoại phê duyệt là phiên chạy đến hết timeout.
  4. Parse JSON, đừng đọc bằng mắt. --output-format json trả về kết quả có cờ lỗi; jq -e '.is_error == false' là tín hiệu duy nhất trong cả pipeline mà bạn có miễn phí.

Sau khi có guard: fail im lặng từ 41 xuống 0 trong 30 ngày kế tiếp, còn fail ồn ào từ 0 lên 19 — chủ yếu là timeout và vài lần bỏ lượt do trùng lịch. Đó mới là trạng thái đúng.

Mô hình tư duy đáng chép lại

Đối xử với claude -p như một lời gọi mạng chập chờn, không phải như một lệnh shell. Lệnh shell trả về 0 là đã làm xong việc. Một request mạng trả về 200 vẫn có thể là trang HTML ghi "dịch vụ tạm ngưng". Bạn phải đọc body. Ở đây cũng vậy.

Một thay đổi nhỏ nữa đáng làm ngay hôm nay: đổi tiêu đề mail báo lỗi từ "job failed" thành thứ nói rõ cái gì thiếu — "chưa sinh được bài cho hôm nay" cho bạn biết có cần quan tâm trước khi kịp uống cà phê hay không.

Không spam, hủy đăng ký bất kỳ lúc nào.

Bài viết liên quan