Chuyện cứ lặp lại
Có người hỏi trong chat: "đơn BK-xxxx fail, check hộ cái".
Câu trả lời là một quy trình mười lăm bước. Tìm xem cái id đó nằm ở nguồn log nào — có bốn nguồn, và chọn nguồn nào thì tuỳ vào loại id người ta đưa. Bật VPN. Phát hiện DB vẫn timeout vì có route bám nhầm interface. Sửa route. Query bảng booking, rồi log booking, rồi webhook log. Cuối cùng mới kết luận được là nhà cung cấp hỏng hay mình hỏng.
Cả team ai cũng làm được. Nhưng không ai làm nhanh được, vì quy trình nằm trong đầu người vừa làm gần đây nhất — còn mấy cái bẫy thì không nằm ở đâu cả. Người mới nào cũng trả đủ giá cho đúng nửa ngày đó.
Tài liệu thì có. Không ai mở. Tài liệu mà phải nhớ ra là có để đọc thì luôn thua một đồng nghiệp ngồi cạnh có thể hỏi trực tiếp.
Chỗ nghĩ thông
Một slash command trong Claude Code là một file markdown. Nội dung file chính là cái prompt. Cơ chế chỉ có thế.
Nghĩa là runbook và tool không còn là hai thứ rời nhau rồi lệch dần — chúng là một file. Viết quy trình ra chính là ship cái tự động hoá. Không còn cái bước thứ hai, nơi ai đó dịch trang wiki thành script rồi trang wiki nằm đó mốc dần.
Thế là bài toán tài liệu biến thành bài toán phân phối, mà phân phối thì mình biết cách giải.
Mình đã làm gì
Hai bộ, mỗi công ty một bộ:
- ~24 lệnh cho team gateway — dựng lại lỗi ở local, lần một id bất kỳ ra thành timeline, dựng lại đúng cái request đã gửi lên nhà cung cấp từ một trace-id, tạo/nhân bản supplier, chạy bộ certification của nhà cung cấp, ship.
- ~18 lệnh cho bên kia — debug một service từ đầu tới cuối, kiểm tra xem tính năng đa repo đã thật sự vào đủ repo chưa, đối soát đơn bảo hiểm với chặng bay, làm UI mà không đẻ ra hệ màu thứ hai, ghi worklog ngày từ git + ticket + chat.
Cố tình để hai bộ chứ không gộp. Hạ tầng khác, tích hợp khác, bẫy khác. Chúng giống nhau ở hình dạng, không giống nhau ở nội dung.
Bốn quyết định làm nên chuyện
1. Mọi lệnh đều khai báo mức sát thương, ngay ở mục lục.
🟢 chỉ đọc 🟠 GHI vào DB production 🔴 DEPLOY lên productionRủi ro khi đưa cho một con agent quyền chạy lệnh không phải là nó làm sai. Rủi ro là nhìn lướt bốn mươi lệnh, bạn không phân biệt được cái nào nguy hiểm. Đặt cái nhãn đó ở mục lục, đừng chôn nó ở đoạn thứ ba trong thân bài, thì người đọc được cảnh báo trước khi kịp tò mò.
2. Mọi thứ có GHI đều đi theo đúng một quy trình, và quy trình nằm trong tool.
Dry run → in ra kế hoạch → xin xác nhận → thực thi → đọc lại để verify.
Trong phần conventions có một câu mình tâm đắc nhất:
Lệnh nào bỏ qua một bước là bug của lệnh đó — đi sửa lệnh, đừng lách bằng cách làm tay.
Đúng một câu đó giữ cho quy trình không mục ra. Khi "thôi lần này thôi" bị coi là một bug report chứ không phải một lối tắt, thì cái rào chắn mới sống sót qua được một ngày xấu trời.
3. Đừng bao giờ đoán connection đang trỏ đi đâu — bắt nó tự chứng minh.
File config local mỗi máy một khác: production, staging, read replica, hay đang
port-forward. Nên trước mọi thao tác ghi, lệnh phải chạy SELECT @@hostname, @@read_only và in kết quả ra cho người xem.
Cái này không phải lo xa. Ở bên kia, cái bẫy đứng số một trong README là: nhiều
service commit file config local trỏ thẳng vào production, phần test bị
comment lại. Clone về, npm run start:dev, thế là cái máy của bạn đang ghi vào
DB production mà không có một dòng cảnh báo nào. Nhận ra bằng tên database —
tên bản test và bản production chỉ khác nhau cái đuôi.
4. Không hardcode bất cứ thứ gì mang tính cá nhân.
Không đường dẫn /Users/..., không email cá nhân, không user id, không IP nội
bộ. Tất cả đi qua biến môi trường, kèm một bảng ghi rõ không set thì hỏng cái
gì. Rồi chốt bằng một lệnh grep trước khi commit:
grep -rn "/Users/\|@gmail\|password.*=.*['\"]" .claude/commands/ # phải rỗngMột giá trị mặc định sai âm thầm còn tệ hơn là bắt người ta tự gõ vào.
Hai thứ nhỏ mà quan trọng hơn mình tưởng
Cài bằng symlink, để git pull chính là cập nhật. Việc áp dụng không chết ở
bước cài, nó chết ở bước nâng cấp. Nếu muốn có bản mới phải chạy lại installer,
thì trong vòng một tháng cả team đang chạy bộ lệnh của tháng trước.
Đặt ngưỡng bảo mật theo ai đọc được repo, không theo chữ "nội bộ". Một trong hai bộ nằm ở repo mà bất kỳ ai vào được mạng công ty đều đọc được — không cần login, không cần được add member. Tệp người đọc đó rộng hơn "repo private" rất nhiều, nên ngưỡng phải cao hơn: không credential, không host thật, không port, không dữ liệu khách — kể cả trong ví dụ. Và phải viết rõ vì sao ngưỡng nằm ở đó, không thì người đóng góp sau sẽ mặc định "nội bộ" nghĩa là "an toàn", một cách rất hợp lý.
Còn đây là quy tắc đóng góp giữ cho hai bộ này sống:
Mất hơn 30 phút vì một cái bẫy? Thêm vào
docs/, kèm nguyên văn thông báo lỗi.
Nguyên văn, vì người sau sẽ không đi tìm bản tóm tắt gọn gàng của bạn. Họ sẽ dán đúng cái chuỗi mà cái máy vừa quăng vào mặt họ.
Điều rút ra
Định dạng tốt nhất cho một runbook là định dạng mà máy chạy được. Không phải vì tự động hoá tiết kiệm được thao tác gõ — có tiết kiệm, nhưng đó là phần thưởng nhỏ. Mà vì một tài liệu được chạy thì không thể âm thầm mốc đi. Nó gãy, gãy ầm ĩ, ngay ngày hệ thống đổi.
Dán nhãn nguy hiểm ở chỗ mắt nhìn vào đầu tiên. Một cảnh báo chôn trong thân bài là cảnh báo dành cho người vốn đã đọc kỹ. Người bạn cần bảo vệ là người đang lướt mục lục lúc 11 giờ đêm.
Đặt rào chắn vào trong tool, đừng đặt vào trong đầu người. "Nhớ dry-run
trước nhé" không phải một cơ chế an toàn, đó là một lời chúc. --dry mặc định,
một bước xác nhận, một lần đọc lại để verify — đó mới là cơ chế, và chúng vẫn
chạy đúng khi người đã mệt.
Ghi lại chỗ mình chưa chắc. Cả hai bộ đều bắt buộc phần "chỗ không chắc" trong kết quả audit. Nó tốn đúng một đoạn văn, và nó là khác biệt giữa một tài liệu đồng nghiệp xây tiếp lên được với một tài liệu họ phải đi verify lại từ đầu.