sáu bài học · đo được, có ngày, có trích

Sáu bài học tôi đo được

Sáu sự cố dưới đây không phải rủi ro giả định. Mỗi sự cố có ngày, có số lần đo, và có một file đang mang luật sinh ra từ nó. Tôi xếp chúng theo stage mà lỗi rơi ra chứ không theo mức nặng nhẹ, vì thứ cần nhớ là chỗ nào trong vòng đời hay thủng. Phần đáng đọc là cách chữa và cái giá của nó. Không cách chữa nào miễn phí.

Ba agent trên một checkout làm mất việc mà không ném lỗi.

Tôi từng chạy ba session trên cùng một checkout và không lỗi nào được ném ra. Một buổi chiều làm việc biến mất, và tôi mất thêm một buổi nữa để tìm ra nguyên nhân.

Tôi chọn cách chữa thô nhất: mọi agent được spawn mà có quyền chạy git đổi trạng thái đều phải có checkout riêng, kể cả agent chỉ đọc. Ngoại lệ “chỉ đọc” chính là giả định tôi đã tin, và nó sai. Cái giá là mỗi Builder tốn thêm một worktree trên đĩa và vài giây setup. Tôi trả giá đó vì buổi chiều kia đắt hơn nhiều.

Bảy stage đánh dấu bảy chỗ đã có thứ rơi ra.

Ban đầu quy trình chỉ có hai stage: agent làm, tôi review. Review kéo từ năm tới mười bốn vòng, và phần lớn vòng sau dùng để dọn thứ vòng trước để lại. Reviewer không phải chỗ hỏng. Chỗ hỏng nằm xa hơn về phía đầu: những quyết định chưa từng được chốt đi thẳng vào code, rồi bị chốt ở điểm đắt nhất của quy trình.

Tôi đẩy vòng lặp lên đầu quy trình và cắt phần còn lại thành bảy stage có tên. Bảy stage có tên để mỗi lỗi đo được gắn vào đúng một stage; không có tên thì lần sau lỗi rơi vào chỗ không ai chỉ ra được. Cái giá là quy trình dài hơn, và ba trong bảy stage vẫn trống vì tôi chưa đo được lỗi nào ở đó.

claim

AST-131promoted 2026-08-22

The queue drained and nothing asked whether a slot was free

Measured on an adapted project running continuously by the owner's instruction: **after two merges, two of four Builder slots sat idle while twelve claimable tickets waited.** Nothing errored. Every step performed was performed correctly.

The router's loop was notification → verify → merge → report → wait, and no step in it asks how many Builders are working. …

measured once, one project — 2 of 4 slots idle after two merges, 12 claimable tickets waiting, nothing errored.

Queue còn mười hai ticket claim được trong khi hai trên bốn slot Builder ngồi không, và không có gì báo lỗi. Vòng lặp của router là nhận thông báo, xác minh, merge, báo cáo, chờ; không bước nào hỏi có bao nhiêu Builder đang chạy.

Tôi sửa bằng cách cho frontier query một mục tiêu chứ không chỉ một trigger: sau mỗi lần merge, router phải hỏi nên claim thêm bao nhiêu ticket, không chỉ hỏi ticket nào claim được. Cái giá là router bận hơn và thỉnh thoảng claim quá tay. Tôi chấp nhận đánh đổi đó, vì một slot ngồi không thì không phát ra tín hiệu nào.

brief

chưa đo được lỗi nào ở đây

build

AST-097promoted 2026-08-18

TERMINAL:done means the turn ended, not that the work finished

Found by workspace-app-inception Thomas, measured three times on one pane in one session. A builder launches a long background process (a test suite, a build), ends its TURN while waiting for the completion notification, and the pane reads done. The watcher faithfully reports TERMINAL:done. …

measured three times, one pane, one session — the dispatcher nearly reported it abandoned before checking further.

Một Builder khởi động tiến trình nền dài rồi kết thúc lượt trong lúc chờ. Pane hiện done, watcher báo TERMINAL:done, và bảng nhánh trong dispatch-ticket nói builder đã xong. Tôi suýt báo ticket đó bỏ dở, trong khi Builder đang ở phút thứ hai mươi của một việc tử tế.

Thứ cứu được tình huống này không phải protocol, mà là artifact tự mâu thuẫn: file bị sửa chỉ có thêm đúng một dòng comment. Từ đó tôi bỏ hoàn toàn niềm tin vào trạng thái pane, và mỗi chữ done phải đọc diff trước khi kết luận. Cái giá là mỗi lần đóng ticket tốn thêm một vòng đọc.

AST-092promoted 2026-08-18

Builder stops after writing code but before committing — pane reads done, cleanup deletes the…

Measured in the field: 5 instances, 3 different Builder sessions, runtime claude, dispatched via herdr pane. Each time the Builder wrote substantial work (93-433 lines), pane status settled to done or idle, watcher returned TERMINAL:done, but git status on the worktree showed uncommitted changes.

The danger: Thomas's cleanup in dispatch-ticket runs git worktree remove, which silently deletes all uncommitted files. …

measured five times, three Builder sessions — 93 to 433 lines lost each time, never recovered.

Vẫn là chữ đó, và lần này hậu quả nặng hơn. Builder viết xong code rồi dừng trước khi commit, pane vẫn về done, và bước cleanup chạy git worktree remove đè lên. Năm lần trên ba session, mỗi lần 93 tới 433 dòng, không lần nào lấy lại được.

Tôi đặt chốt chặn ngay tại bước nguy hiểm thay vì trông vào việc Builder commit cẩn thận hơn. Cleanup phải đọc git status của worktree trước khi xoá; thấy dirty là dừng và đưa lại cho người. Cái giá là worktree mồ côi tồn đọng và thỉnh thoảng phải dọn tay. So với mất một ngày làm việc thì giá đó rẻ.

code-review

chưa đo được lỗi nào ở đây

simplify

chưa đo được lỗi nào ở đây

arm

AST-015promoted 2026-07-11

Export step committed live secrets + buyer PII

Same-vendor correctness review PASSED it; cross-vendor caught it (P1) — the two lenses catch different classes. A value that touched a tracked file is burned — rotate it. Bound: .claude/rules/no-secrets-in-exports.md (always-on). CI secret-scan still TODO.

one measured incident — a same-vendor review passed it; a cross-vendor reviewer caught it before it shipped further.

Một bước export commit thẳng secret sống và PII của người mua vào file được track. Vòng review same-vendor đọc qua và cho pass. Vòng cross-vendor bắt được và xếp mức P1.

Đó là lý do cuối phase vẫn còn một vòng arm chạy bằng vendor khác, dù tốn thêm tiền và thời gian. Hai lăng kính bắt hai lớp lỗi khác nhau, và lớp mà same-vendor bỏ sót là lớp đắt nhất khi lọt. Một giá trị đã chạm vào file được track thì coi như cháy và phải rotate. Phía sau không có phương án nào rẻ hơn.

merge

AST-074promoted 2026-08-18

A tracker measured only against itself cannot detect its own drift

Reported upstream from an adapted project's own Thomas, via a handoff, rather than found in this package directly — the first entry with that provenance, recorded because the class it names is general and the reachability checks have no way to see a tracker's *content* going stale, only a document's.

Four tickets in that project sat claimed and in-progress with a live assignee **after their code had merged to the base branch**, the oldest by a full day. …

measured once, one project — 4 tickets stale, the oldest by a full day, before anyone noticed.

Bốn ticket nằm in-progress với assignee sống sau khi code đã merge, ticket cũ nhất trễ trọn một ngày. Không có gì báo lỗi: merge chạy, phần ghi frontier ngược lại thì không, và không artifact nào ghi lại chỗ thiếu đó.

Không check nào chỉ nhìn tracker mà bắt được lỗi này, vì một trạng thái sai vẫn nhất quán với chính nó. Tracker phải được đối chiếu với Git sau mỗi lần merge, thay vì tự xác nhận chính nó. Cái giá là thêm một bước reconcile không ai vui khi chạy, và phần lớn thời gian nó không tìm ra gì.

AST-056promoted 2026-08-12

A blocking edge expresses order, not exclusion

The frontier asks which tickets have no open blocker and no assignee. It never asks what each ticket will WRITE, and nothing else did either. Two tickets went out together, correct by every rule the package stated. One existed to correct WIRE-CONTRACT.md; …

measured twice in one day — a naive merge would have reverted already-reviewed work.

Hai ticket không có blocking edge nào giữa chúng, đúng mọi luật tôi đã viết, và cùng sửa ba dòng của một file. Ticket thứ nhất merge trước. Ticket thứ hai dựng trên commit trước cú merge đó, đẻ ra hai khối conflict, và một cú merge sai chiều sẽ revert phần đã review mà không phát ra tín hiệu nào.

Một worktree cho mỗi Builder chỉ giải quyết va chạm ở checkout. Nó dời va chạm xuống merge, chỗ tìm ra muộn và phải xử tay. Vì vậy ticket phải khai write-set, và những ticket có write-set giao nhau bị xếp tuần tự kể cả khi không có gì buộc chúng theo thứ tự. Cái giá là số ticket chạy song song giảm xuống, đúng thứ mà cả hệ thống này tồn tại để tăng lên.

Khám phá toàn bộ ledger →