Khi coding agent bị kẹt hoặc đi sai hướng
Cách dừng một vòng sửa mãi không xong, dựng giả thuyết có căn cứ rồi quyết định tiếp tục, bàn giao hay hỏi con người.
24 min read

Ở bài trước, mình đã chốt trạng thái, review diff và chạy các bước kiểm tra trước khi tin lời agent báo done.
Nhưng nếu test vẫn đỏ hết vòng này tới vòng khác thì sao?
Agent sửa một chỗ. Test vẫn đỏ. Nó đổi sang cách khác. Một test xanh lên thì hai test khác lại đỏ. Sau vài vòng, diff lớn gấp ba nhưng mình vẫn chưa biết lỗi ban đầu nằm ở đâu.
Đây là lúc một coding agent dễ biến từ người làm việc rất nhanh thành một người thử mọi thứ cũng rất nhanh.
Phản xạ thường gặp là nhắc thêm “hãy suy nghĩ kỹ hơn”, “thử lại” hoặc đổi model. Đôi khi có tác dụng. Nhưng nếu mỗi vòng không tạo ra bằng chứng mới, thêm token chỉ giúp agent chạy nhanh hơn quanh đúng cái vòng cũ.
Bài này nói về lúc cần dừng tay. Mình sẽ tách vấn đề nằm ở yêu cầu, ngữ cảnh, cách tái hiện lỗi, môi trường hay phần code; dựng vài giả thuyết có thể kiểm tra; rồi chọn tiếp tục session, mở session mới hay đưa quyết định về cho con người.
Agent bị kẹt không có nghĩa là nó ngừng trả lời
Một agent có thể trả lời liên tục nhưng công việc vẫn đứng yên.
Với mình, bị kẹt là khi mỗi vòng không làm mình hiểu vấn đề hơn. Agent vẫn đọc file, sửa code và chạy câu lệnh, nhưng sau vòng đó mình chẳng biết thêm điều gì đáng tin về nguyên nhân lỗi.
Một vòng debug tốt thường tạo ra ít nhất một trong ba thứ:
- Một giả thuyết bị loại
- Một vùng nghi ngờ được thu hẹp
- Một bằng chứng mới làm thay đổi hướng điều tra
Nếu không có thứ nào xuất hiện, đó chỉ là nhiều hoạt động chứ chưa chắc có tiến triển. Quạt quay rất nhanh nhưng phòng chưa chắc mát.
Mình thường để ý các dấu hiệu sau:
- Agent lặp lại cùng một giải thích bằng từ khác
- Diff chuyển qua lại giữa hai cách làm
- Test đỏ đổi chỗ nhưng nguyên nhân gốc chưa rõ hơn
- Agent thêm thử lại (
retry), chờ thêm (delay), dữ liệu giả (mock) hoặc gom mọi lỗi vào mộtcatchchung để làm lỗi biến mất - Phạm vi mở rộng dần sang cấu hình, dependency hoặc module không liên quan
- Câu lệnh trả lỗi nhưng agent bỏ qua rồi kết luận bằng suy đoán
- Bản tóm tắt cuối rất tự tin nhưng bằng chứng vẫn giống vòng trước
- Agent liên tục nói cần "một fix nhỏ nữa"
Một lần lặp chưa phải vấn đề. Debug vốn là quá trình thử và quan sát.
Nhưng nếu cùng một lỗi xuất hiện hai hoặc ba lần mà không có bằng chứng mới, mình dùng một quy tắc dừng đơn giản:
Quy tắc dừng không phải bỏ cuộc. Nó chỉ đổi từ chế độ sửa code sang chẩn đoán.

Đừng dọn mất bằng chứng
Khi agent bắt đầu kẹt, không bảo nó reset, xoá log, viết lại cả module hoặc dọn thư mục làm việc cho sạch. Trước hết phải giữ lại trạng thái đang lỗi và những gì đã quan sát được.
Đóng băng trạng thái trước khi chẩn đoán
Ở bài 07, mình đóng băng trạng thái để review diff cuối.
Ở đây mình đóng băng vì một lý do khác: cần giữ nguyên hiện trường đủ lâu để hiểu lỗi.
Nếu agent vừa điều tra vừa thay code liên tục, mỗi quan sát có thể đến từ một trạng thái khác nhau. Test lỗi lần đầu trên cách làm A. Log lần sau lại đến từ cách làm B. Tới lúc so sánh, cả hai không còn cùng một thí nghiệm.
Ảnh chụp trạng thái tối thiểu của mình thường gồm:
Sau đó ghi lại:
Google SRE xem điều mong đợi, điều thực tế và cách tái hiện là những phần cốt lõi của một báo cáo lỗi tốt. Với coding agent, mẫu này cũng rất hợp vì nó buộc cuộc nói chuyện quay về điều quan sát được.
Đừng chỉ ghi "test invitation đang lỗi".
Một báo cáo hữu ích hơn sẽ là:
Báo cáo này chưa nói nguyên nhân gốc là gì. Chính vì vậy nó tốt.
Nó mô tả lỗi mà chưa trộn giả định vào điều đã biết.
Trước khi sửa code, phân loại thứ đang sai
Nhiều vòng lặp kéo dài vì agent mặc định mọi lỗi đều nằm ở phần code.
Thực tế, ít nhất có năm nhóm khác nhau.
| Nhóm | Dấu hiệu | Câu hỏi cần trả lời |
|---|---|---|
| Yêu cầu hoặc quyết định | Hai cách hoạt động đều nghe hợp lý, tiêu chí hoàn thành không phân biệt được | Người dùng thật sự muốn điều gì? Ai có quyền chốt? |
| Ngữ cảnh hoặc cách hiểu | Agent gọi nhầm abstraction, bỏ qua sổ tay project, hiểu sai luồng dữ liệu | Nguồn chuẩn nào đang thiếu hoặc đã cũ? |
| Cách tái hiện | Test lúc xanh lúc đỏ, bước chạy không ổn định, không tái hiện được lỗi | Lỗi nào là tín hiệu thật? Có trường hợp tối thiểu chưa? |
| Môi trường hoặc công cụ | Sai phiên bản, thiếu service, thông tin đăng nhập, cổng, dữ liệu test hoặc đầu ra sinh tự động | Câu lệnh có đang chạy trong môi trường hợp lệ không? |
| Giả thuyết về phần code | Lỗi ổn định, yêu cầu rõ, môi trường đáng tin | Cơ chế nào trong code có thể tạo ra kết quả này? |
Phân loại này không cần đúng tuyệt đối ngay từ đầu.
Nó chỉ giúp mình không giao nhầm loại công việc.
Nếu yêu cầu mơ hồ, agent cần đưa ra quyết định cần chốt chứ không nên tự chọn cách hoạt động rồi sửa test theo.
Nếu ngữ cảnh sai, thêm một nguồn chuẩn chính xác hữu ích hơn thêm một đoạn prompt dài.
Nếu cách tái hiện không ổn định, việc đầu tiên là thu nhỏ trường hợp và kiểm soát đầu vào. Không nên bắt agent sửa code chạy thật theo một lỗi lúc có lúc không.
Nếu môi trường hỏng, đổi assertion để bộ test xanh chỉ che mất vấn đề.
Chỉ khi bốn nhóm đầu đủ rõ, mình mới tập trung vào giả thuyết về phần code.
Có thể có nhiều nguyên nhân cùng lúc
Một test có thể vừa lúc xanh lúc đỏ vì môi trường, vừa phát hiện một race condition thật. Phân loại không nhằm ép mọi lỗi vào đúng một ô. Nó giúp mình biết bằng chứng nào còn thiếu trước khi sửa tiếp.
Tách điều đã biết, điều đang đoán và điều chưa biết
Agent rất giỏi nối những mảnh thông tin thành một câu chuyện trôi chảy.
Khi gỡ lỗi, điểm mạnh đó cũng là một cái bẫy. Một giả định nghe hợp lý có thể bị lặp lại vài lần rồi dần được đối xử như điều đã biết.
Mình dùng ba cột rất đơn giản:
| Loại | Ví dụ trong luồng lời mời |
|---|---|
| Điều đã biết | Test đồng thời tạo hai bản ghi; schema hiện tại không có ràng buộc duy nhất phù hợp |
| Giả định | Hai insert đi qua cùng một khoảng hở giữa bước kiểm tra và bước ghi |
| Điều chưa biết | Mức cô lập transaction hiện tại có tuần tự hoá hai yêu cầu không? |
Điều đã biết phải trỏ được tới bằng chứng như diff, kết quả test, schema, log hoặc trạng thái database.
Giả định là cách giải thích tạm thời.
Điều chưa biết là câu hỏi chưa có đủ bằng chứng để trả lời.
Nếu một dòng không biết nên đặt ở đâu, mình hỏi: “Có quan sát nào chứng minh điều này chưa?”
Chưa có thì nó chưa phải điều đã biết.
Đây cũng là lúc mình xoá bớt ngữ cảnh không còn đáng tin. Bản tóm tắt cũ nói module có transaction không quan trọng bằng việc mở code hiện tại ra kiểm tra. Một lỗi từ ba vòng trước không quan trọng bằng kết quả tái hiện trên trạng thái đang giữ.
Dùng giả thuyết để chọn phép thử tiếp theo
Chẩn đoán không phải liệt kê mười nguyên nhân có thể xảy ra rồi chọn cái nghe thông minh nhất.
Mỗi giả thuyết cần dẫn tới một phép thử có khả năng bác bỏ nó.
Mình hay dùng bảng này:
| Giả thuyết | Bằng chứng ủng hộ | Bằng chứng chống lại | Phép thử rẻ nhất | Kết quả |
|---|---|---|---|---|
| Không có ràng buộc duy nhất | Schema không có ràng buộc | Chưa có | Đọc migration và xem schema database test | Chưa chạy |
| Service kiểm tra rồi ghi ngoài transaction | Hai yêu cầu cùng tạo bản ghi | Chưa đọc ranh giới | Lần theo đường code và transaction wrapper | Chưa chạy |
| Test dùng hai database khác nhau | Dữ liệu test có tách theo agent | Cả hai bản ghi xuất hiện trong cùng assertion | In định danh database của từng yêu cầu | Chưa chạy |
| Email chuẩn hoá không nhất quán | Đầu vào khác chữ hoa, chữ thường | Lỗi vẫn xảy ra với email giống hệt | Chạy trường hợp với cùng một giá trị | Bị loại |
Phép thử tốt không nhất thiết phải sửa code.
Nó có thể là:
- Đọc đúng một hàm và nơi gọi nó
- Chạy một test liên quan với đầu vào được kiểm soát
- Truy vấn trạng thái trước và sau câu lệnh
- So sánh commit tốt và commit lỗi
- Bật log có giới hạn trong môi trường test
- Thay một dependency thật bằng bản giả có thể quan sát
- Chạy cùng câu lệnh ở một môi trường sạch
Nếu lỗi chỉ xuất hiện sau một khoảng commit, git bisect có thể tìm nhị phân để xác định commit đầu tiên đưa bug vào. Đây là ví dụ đẹp của việc thu hẹp không gian tìm kiếm thay vì đọc toàn bộ lịch sử theo cảm giác.
Google SRE cũng mô tả gỡ lỗi như một vòng lặp giữa quan sát, giả thuyết và test. Họ khuyên thử nguyên nhân có xác suất cao trước, cân nhắc rủi ro của phép thử và ghi lại cả kết quả âm tính.
Kết quả âm tính không phải phí thời gian.
Nếu một phép thử đủ rõ để loại một giả thuyết, không gian điều tra đã nhỏ đi. Đó là tiến triển.

Vòng gỡ rối mình thường dùng
Sau khi dừng vòng lặp cũ, mình đi theo bảy bước.
Bước 1: Chốt trạng thái.
Dừng sửa. Giữ thư mục làm việc, lỗi chính xác và câu lệnh tái hiện. Nếu lỗi có thể làm hỏng dữ liệu hoặc trạng thái dùng chung, chặn tác động phụ trước rồi mới điều tra.
Bước 2: Mô tả vấn đề.
Viết điều mong đợi, thực tế, cách tái hiện và phạm vi. Nếu chưa tái hiện được, đánh dấu rõ thay vì để agent giả vờ đã có trường hợp ổn định.
Bước 3: Phân loại.
Xác định vấn đề nghiêng về yêu cầu, ngữ cảnh, cách tái hiện, môi trường hay phần code. Ghi nhiều nhóm nếu bằng chứng đang chỉ về nhiều phía.
Bước 4: Tách riêng.
Tách điều đã biết, giả định và điều chưa biết. Bỏ các kết luận không còn trỏ tới trạng thái hiện tại.
Bước 5: Dựng giả thuyết.
Tạo từ hai tới bốn giả thuyết có xác suất hợp lý. Mỗi giả thuyết phải có một cách kiểm tra có thể làm nó mạnh hơn hoặc yếu đi.
Bước 6: Chạy phép thử.
Chạy phép thử rẻ, chỉ đọc và ít tác động phụ trước. Mỗi lượt chỉ nên thay một biến chính. Ghi câu lệnh, kết quả và giả thuyết nào đã bị loại.
Bước 7: Quyết định.
Khi bằng chứng đủ rõ, chọn một trong bốn hướng:
Sau một lần sửa, mình chạy lại cách tái hiện ban đầu rồi chạy regression test gần vùng bị ảnh hưởng.
Nếu lỗi đổi hình dạng, quay lại bước mô tả vấn đề. Đừng mang nguyên giả thuyết cũ sang một vấn đề mới chỉ vì chúng nằm trong cùng session.
Prompt để chuyển agent sang chế độ chẩn đoán
Khi agent bắt đầu thử lung tung, prompt dài thêm về cách làm thường không giúp nhiều.
Mình chuyển chế độ thật rõ:
Giới hạn bốn giả thuyết khá hữu ích. Nếu không, agent dễ tạo cả cuốn catalogue nguyên nhân mà chẳng ưu tiên phép thử nào.
Mình cũng nói rõ phép thử phải có mục đích bác bỏ. Nếu agent chỉ chạy thêm cả bộ test mà không nói kết quả sẽ thay đổi giả thuyết nào, câu lệnh đó có thể chỉ tạo thêm log.
Khi nào giữ session hiện tại
Không phải thấy agent kẹt là xoá ngữ cảnh ngay.
Mình tiếp tục session hiện tại khi:
- Agent vẫn phân biệt được điều đã biết và giả định
- Cách tái hiện đã ổn định
- Ngữ cảnh đang có còn đúng với repository
- Mỗi phép thử vẫn loại được một giả thuyết
- Thư mục làm việc và phạm vi chưa trở nên hỗn loạn
- Quyết định cần thiết đã có
Session hiện tại có một lợi thế: nó giữ lịch sử thí nghiệm vừa chạy. Nếu lịch sử đó sạch và có ghi chép, bỏ đi ngay cũng lãng phí.
Nhưng mình mở session mới khi:
- Agent lặp lại giả định đã bị bằng chứng bác bỏ
- Bản tóm tắt bắt đầu mâu thuẫn với file hoặc câu lệnh hiện tại
- Quá nhiều patch tạm, log và nhánh điều tra làm ngữ cảnh nhiễu
- Agent không còn nhớ trạng thái nào tạo ra lỗi nào
- Cùng một hướng sửa đã quay lại nhiều lần
- Có thể tạo một bản bàn giao ngắn, độc lập và đáng tin
Session mới không nên nhận toàn bộ đoạn chat cũ.
Nó cần một bản bàn giao để tiếp tục gỡ lỗi:
Bản bàn giao tốt không kể lại drama của session cũ.
Nó giữ những thứ có giá trị: trạng thái cuối, bằng chứng, kết quả âm tính, điều chưa biết và phép thử tiếp theo.
Khi nào phải đưa quyết định về cho con người
Có những lúc agent không bị kẹt về kỹ thuật. Nó bị kẹt vì chưa ai quyết định sản phẩm phải hoạt động thế nào.
Ví dụ invitation trùng nên:
- Trả về invitation cũ
- Tạo token mới và vô hiệu token cũ
- Trả conflict
- Hay âm thầm thành công để API có tính idempotent
Đây không phải câu hỏi có thể giải bằng cách đọc thêm code nếu repository chưa có thoả thuận.
Mình dừng và hỏi con người khi:
- Hai cách hoạt động đều hợp lý nhưng ảnh hưởng người dùng khác nhau
- Cần thay API công khai, schema hoặc chiến lược migration
- Cách sửa phải chấp nhận mất dữ liệu, rủi ro bảo mật hoặc mất tương thích ngược
- Thiếu thông tin đăng nhập, quyền truy cập hoặc sự đồng ý để kiểm tra môi trường thật
- Phạm vi mới lớn hơn task đã được duyệt
- Bằng chứng cho thấy nguồn chuẩn hiện tại tự mâu thuẫn
Agent nên trả một bản tóm tắt quyết định ngắn:
Đề xuất vẫn có giá trị. Nhưng quyền quyết định phải nằm đúng chỗ.
Ví dụ: agent cứ thêm retry cho lời mời trùng
Quay lại workspace invitation.
Test đồng thời cho thấy hai yêu cầu cùng email có thể tạo hai lời mời đang chờ. Agent nhìn lỗi rồi thêm một lần truy vấn lại sau khi ghi. Test vẫn lúc xanh lúc đỏ. Nó thêm retry với delay. Test qua một lần, đỏ lần sau.
Nếu tiếp tục bảo “sửa cái test chập chờn này”, agent có thể thêm nhiều mẹo thời gian hơn cho tới khi CI tình cờ xanh.
Mình dừng lại và chụp vấn đề:
Từ đây có ba giả thuyết gần nhất:
- Test không thật sự chạy hai yêu cầu trên cùng trạng thái database.
- Bước kiểm tra trùng và ghi dữ liệu không phải một thao tác nguyên tử.
- Schema database không ép buộc quy tắc mà service đang hứa.
Phép thử rẻ nhất không phải thêm delay.
Mình xem dữ liệu test để xác nhận database dùng chung, lần theo ranh giới transaction và đọc migration hiện tại. Nếu bằng chứng cho thấy hai yêu cầu cùng kiểm tra rồi ghi ngoài một ràng buộc đủ mạnh, retry theo thời gian không giải quyết được quy tắc dữ liệu.
Lúc đó còn một quyết định thật: quy tắc nên được giữ bằng ràng buộc duy nhất nào, và API nên trả gì khi hai yêu cầu đụng nhau.
Sau khi chốt quyết định, agent mới có một cách sửa trong phạm vi rõ:
- Thêm hoặc chỉnh ràng buộc database phù hợp
- Bắt
conflicttheo thoả thuận đã chọn - Giữ test tích hợp đồng thời làm bằng chứng chống lỗi tái xuất hiện
- Chạy lại test phân quyền và tác động phụ của email
Điểm khác biệt không nằm ở việc agent thông minh hơn sau prompt mới.
Điểm khác biệt là vòng phục hồi đã biến một triệu chứng mơ hồ thành điều đã biết, giả thuyết, phép thử và quyết định cụ thể.
Khi nhiều agent cùng debug
Ở bài 06, mình có nói nhiều agent chỉ đọc có thể kiểm tra các giả thuyết độc lập.
Cách đó hữu ích khi không gian điều tra đủ rộng. Một agent kiểm tra môi trường, một agent lần theo luồng dữ liệu, một agent đọc lịch sử lỗi tái xuất hiện.
Nhưng agent chính vẫn phải giữ một bảng giả thuyết chung.
Nếu mỗi agent tự sửa theo giả thuyết riêng, trạng thái cuối sẽ không còn là một phép thử có kiểm soát. Hai thay đổi cùng lúc có thể làm test xanh mà mình không biết thay đổi nào thật sự liên quan.
Vì vậy khi gỡ lỗi song song, mình giữ quy tắc:
- Agent phụ ưu tiên chỉ đọc
- Mỗi agent nhận một giả thuyết và đầu ra riêng
- Phát hiện phải có bằng chứng và file tham chiếu
- Kết quả âm tính được ghi lại, không chạy lại ở agent khác
- Chỉ một người được sửa vùng file tại một thời điểm
- Agent chính chọn phép thử hoặc cách sửa tiếp theo trên trạng thái đã ghép
Nhiều agent giúp giảm thời gian tìm bằng chứng.
Chúng không thay thế kỷ luật của việc chỉ thay một biến chính rồi quan sát kết quả.
Với Knowns, đừng để cùng một ngõ cụt lặp qua nhiều session
Khi dùng Knowns, mình ghi điều mong đợi, thực tế, cách tái hiện, điều đã biết và giả thuyết còn mở vào ghi chú task.
Mỗi phép thử có câu lệnh, kết quả và kết luận ngắn. Nếu một giả thuyết đã bị loại, session sau không cần đốt token để phát minh lại nó.
Quyết định thật sự ảnh hưởng cách hệ thống hoạt động thì nằm trong tài liệu hoặc bản ghi quyết định phù hợp. Task chỉ liên kết tới nguồn đó.
Memory chỉ nên giữ cách làm có thể tái sử dụng, không giữ mọi log gỡ lỗi của một lỗi cụ thể.
Mình thích ranh giới này:
Knowns không tự tìm nguyên nhân gốc.
Nó giúp bằng chứng, kết quả âm tính và quyết định không biến mất khi mình đổi agent hoặc ngữ cảnh bị rút gọn.
Checklist gỡ rối nhanh
Khi thấy coding agent bắt đầu đi vòng, mình dùng checklist này:
- Cùng một lỗi đã lặp lại mà không có bằng chứng mới chưa?
- Agent đã dừng sửa và giữ nguyên trạng thái chưa?
- Điều mong đợi, thực tế và cách tái hiện có đủ cụ thể không?
- Lỗi thuộc yêu cầu, ngữ cảnh, cách tái hiện, môi trường hay phần code?
- Điều đã biết có trỏ tới bằng chứng không?
- Giả định nào đang được nói như điều đã biết?
- Có từ hai tới bốn giả thuyết được ưu tiên không?
- Phép thử tiếp theo sẽ bác bỏ giả thuyết nào?
- Phép thử có tác động phụ hoặc làm mất bằng chứng không?
- Kết quả âm tính đã được ghi lại chưa?
- Session hiện tại còn đáng tin không?
- Bản bàn giao có đủ trạng thái, điều đã biết, giả thuyết đã loại và việc tiếp theo không?
- Có quyết định nào agent không có quyền tự chốt không?
- Cách sửa cuối có phạm vi rõ và được kiểm tra lại bằng cách tái hiện ban đầu không?
Không phải lần nào mình cũng viết đầy đủ bảng.
Với bug nhỏ, vài dòng ghi chú là đủ. Nhưng nguyên tắc vẫn giữ nguyên: đừng sửa nhanh hơn tốc độ mình hiểu lỗi.
Kết
Coding agent bị kẹt không đáng sợ bằng việc nó đi rất nhanh mà mình không nhận ra nó đang đi vòng.
Khi một vòng không tạo bằng chứng mới, mình dừng sửa. Mình giữ trạng thái, viết lại vấn đề, tách điều đã biết khỏi giả định, lập vài giả thuyết có thể bị bác bỏ và chạy phép thử rẻ nhất trước.
Từ đó hướng đi trở nên rõ hơn:
- Có bằng chứng thì tiếp tục với một cách sửa trong phạm vi rõ
- Ngữ cảnh đã nhiễu thì bàn giao sang session mới
- Thiếu quyết định về sản phẩm hoặc kiến trúc thì hỏi con người
- Thiếu phần phụ thuộc hoặc quyền truy cập thì ghi chỗ đang chặn thật trung thực
Xoá ngữ cảnh không phải phép chữa mọi lỗi. Prompt dài hơn cũng vậy.
Thứ giúp agent thoát kẹt là một vòng phản hồi tốt hơn, nơi mỗi lượt phải làm giảm phần chưa chắc chắn.
Bài tiếp theo mình sẽ đi thêm một bước: đặt hàng rào nào trước khi agent chạy câu lệnh, sửa file hoặc chạm vào dữ liệu có rủi ro, để việc phục hồi không chỉ bắt đầu sau khi thiệt hại đã xảy ra.
See yah.
Tài liệu tham khảo
Powered by giscus · GitHub Discussions