Một lần tôi giao agent sửa lỗi build markdown. Lỗi thật chỉ là một code fence thiếu language. Agent đọc log, sửa đúng fence, rồi tiện tay “chuẩn hóa” thêm vài heading, đổi title, chỉnh description, và update tracker. Build pass. Diff thì sai scope. Nếu merge, lỗi technical hết nhưng content drift.

Khi agent sửa sai, phản xạ dễ nhất là bảo nó “fix lại”. Nhưng nếu không dừng lại, nó sẽ tiếp tục xây trên trạng thái đã sai. Debug AI coding giống xử lý incident nhỏ: freeze blast radius, lấy evidence, xác định rollback point, rồi mới quyết định patch tiếp hay revert.

Dừng mở rộng diff trước

Command đầu tiên của tôi không phải test. Là:

git status --short
git diff --stat

Tôi muốn biết agent đã chạm file nào. Nếu file list đã vượt scope, đừng cho nó chạy thêm formatter, build fixer, hoặc “cleanup”. Mỗi command tự động có thể làm diff rộng hơn.

Ở bước này, tôi phân loại file:

  • File đúng scope và có thể giữ.
  • File đúng scope nhưng logic sai.
  • File ngoài scope, cần revert hoặc bỏ qua tùy ownership.
  • File generated/lock/env, cần kiểm tra kỹ.

Trong môi trường nhiều worker, nguyên tắc quan trọng là không revert file mình không sở hữu nếu chưa chắc diff là của mình. Có thể file ngoài scope do worker khác đang sửa. Vì vậy “revert tất cả” là nguy hiểm. Tôi thường dùng git diff -- <file> để xác định dấu vết trước khi quyết định.

Đọc log thật, không đọc paraphrase của agent

Agent hay tóm tắt lỗi nghe hợp lý nhưng thiếu dòng quan trọng. Khi debug, tôi muốn command output thật hoặc log thật. Ví dụ build fail:

npm run build

Rồi đọc dòng lỗi đầu tiên có stack/file/line. Không cần đọc 500 dòng. Nhưng phải biết lỗi đến từ đâu. Nếu agent nói “TypeScript issue in hook”, tôi vẫn mở lỗi thật. Có nhiều lần lỗi thật là import path, còn hook chỉ là nơi compiler dừng.

Với production-ish issue, log thật còn quan trọng hơn:

  • Request id nào?
  • Status code nào?
  • Error payload shape ra sao?
  • Retry có chạy không?
  • Database row nào đổi?
  • Timestamp trước/sau deploy?

Không có evidence thì agent sẽ đoán. Đoán nhanh là điểm mạnh của AI khi brainstorming, nhưng là điểm yếu khi debug incident.

Tạo một snapshot tinh thần của trạng thái hiện tại

Trước khi sửa tiếp, tôi ghi lại trạng thái:

Current state:
- Build fail vì MDX parse error ở post X dòng Y.
- Agent đã sửa post X và tracker Z.
- Tracker Z ngoài scope của worker này.
- Mục tiêu bây giờ: chỉ giữ fix code fence, bỏ content rewrite.

Đoạn này có thể nằm trong prompt tiếp theo, không cần thành file. Nó giúp agent không lặp lại hướng sai. Khi context đã lộn xộn, một state summary ngắn hiệu quả hơn việc mắng “bạn làm sai rồi”.

Tôi cũng hay dùng câu:

Không mở rộng scope. Không sửa file mới. Chỉ phân tích diff hiện tại và đề xuất patch nhỏ nhất.

Đây là phanh. Khi agent đã đi sai, prompt tiếp theo phải giảm quyền tự do.

Revert hay patch tiếp?

Không phải lỗi nào cũng nên revert toàn bộ. Tôi chọn theo ba câu hỏi:

  1. Diff sai có nhỏ và hiểu được không?
  2. Có phần đúng nào đáng giữ không?
  3. Rollback có ảnh hưởng file người khác không?

Nếu diff nhỏ, patch tiếp có thể nhanh: bỏ vài dòng, restore title, giữ fix chính. Nếu diff rộng hoặc đụng nhiều shared file, revert phần của agent rồi làm lại thường rẻ hơn. Nếu đang trong dirty worktree có nhiều worker, tôi tuyệt đối tránh command rộng kiểu restore toàn repo.

Patch tiếp phù hợp khi:

  • Một file đúng scope.
  • Lỗi nằm ở vài dòng.
  • Test/verification đã chỉ ra vấn đề rõ.

Revert phù hợp khi:

  • Agent đổi thiết kế module không được yêu cầu.
  • File ngoài scope nhiều.
  • Generated/lockfile bị churn.
  • Không còn tự tin phân biệt fix thật với noise.

Một kỹ năng quan trọng khi dùng AI là biết bỏ diff. Đừng giữ code chỉ vì nó đã được viết.

Debug prompt nên chứa evidence, không chứa cảm xúc

Prompt “sai rồi sửa lại” thường tạo vòng lặp. Prompt tốt hơn:

Build fail:
<paste 20 lines relevant log>

Diff hiện tại đã vượt scope:
- Allowed: src/content/blog/post-a.md
- Unexpected: content-audit/blog-post-status.json

Mục tiêu: sửa MDX parse error trong post-a.md.
Không sửa tracker. Không đổi title/description/body ngoài code fence lỗi.
Trước khi edit, chỉ ra dòng cần sửa.

Agent phản hồi tốt hơn khi có log, file ownership, và constraint cụ thể. Nếu muốn nó tự phân tích, bắt nó chỉ ra dòng trước khi edit. Điều này giảm khả năng nó “rải fix” khắp nơi.

Khi agent tạo bug mới

Bug mới thường đến từ một trong bốn nguồn:

  • Nó hiểu sai contract hiện tại.
  • Nó sửa symptom thay vì cause.
  • Nó thêm fallback che lỗi.
  • Nó refactor quanh bug và làm lệch behavior khác.

Cách debug là quay lại contract. Ví dụ UI không hiển thị error. Đừng hỏi “vì sao component lỗi?” Hỏi:

  • Backend trả payload gì?
  • Hook normalize ra shape gì?
  • Component đọc field nào?
  • Test đang mock ở lớp nào?

Đi từng bước theo data flow. AI có thể giúp trace nhanh, nhưng đừng để nó nhảy từ symptom sang patch. Với mỗi bước, yêu cầu evidence: file path, line, command output, test result.

Nếu bug liên quan state, tôi thích thêm temporary logging trong đầu mình, nhưng không để debug log ở final diff. Với agent, nói rõ:

Bạn có thể đề xuất log để debug, nhưng phải remove trước khi kết thúc.

Dirty worktree cần kỷ luật riêng

Debug trong clean branch đã khó. Debug trong dirty worktree nhiều worker còn khó hơn. Tôi dùng vài rule:

  • Không chạy formatter toàn repo.
  • Không update dependency nếu task không yêu cầu.
  • Không sửa shared file để “làm build xanh” nếu shared file không thuộc scope.
  • Không revert file ngoài ownership.
  • Luôn chạy verification chỉ trên file/scope mình chạm nếu có thể.

Ví dụ với markdown worker, command cuối có thể là:

git diff --check -- src/content/blog/post-a.md

Không cần chạy script audit toàn repo nếu script đó rewrite tracker hoặc normalize hàng trăm file. Verification phải tương thích với môi trường song song.

Khi nào cần human take over

Có những lúc nên dừng agent:

  • Nó lặp lại cùng lỗi sau hai lần correction.
  • Nó không thể giải thích diff của chính nó.
  • Nó muốn đổi contract để làm test pass.
  • Nó đề xuất xóa hoặc migrate dữ liệu thật.
  • Nó không phân biệt file user sửa và file nó sửa.

Human take over không phải thất bại. Đó là kiểm soát rủi ro. Tôi thường tự sửa patch nhỏ, rồi có thể dùng agent khác review lại diff. Đổi vai từ implementer sang reviewer thường hiệu quả hơn bắt cùng một agent tiếp tục đào.

Postmortem nhỏ sau khi sửa xong

Sau một lần agent sửa sai, tôi dành vài phút ghi lại nguyên nhân trong prompt/rule nếu nó có khả năng lặp:

  • Scope chưa ghi file ownership.
  • Không nói “không sửa tracker”.
  • Verification command quá rộng.
  • Thiếu boundary “không đổi title/body”.
  • Không yêu cầu đọc existing helper trước.

Đây là cách workflow tốt dần. Không cần viết quy trình dài. Chỉ cần biến một lỗi thật thành một constraint ngắn cho lần sau.

Kết thúc bằng diff sạch

Khi đã sửa xong, tôi quay lại ba command đơn giản:

git status --short
git diff --stat
git diff --check -- <files>

Nếu task có test, chạy test. Nếu là UI, verify flow. Nếu là blog, kiểm markdown/frontmatter/build khi cần. Sau đó báo rõ:

  • File đã sửa.
  • Command đã chạy.
  • Có file ngoài scope không.
  • Còn rủi ro gì.

Debug agent sửa sai không phải chuyện hiếm. Nó là một phần bình thường của AI coding. Điểm khác biệt giữa workflow bền và workflow mệt là mình có phanh: dừng mở rộng diff, đọc evidence thật, giữ rollback point, và sửa prompt trước khi sửa code tiếp.