Đây là một debug session tôi đã chứng kiến năm ngoái.

Developer nhận ra dark mode của blog bị nhấp nháy khi load trang. Họ nhờ Claude fix. Claude đọc CSS. Nó tìm được nguyên nhân có vẻ hợp lý. Nó thay đổi. Developer refresh. Vẫn nhấp nháy. Claude đoán thêm lần nữa. Refresh. Vẫn nhấp nháy. Lần thứ ba. Refresh. Nhấp nháy biến mất — nhưng giờ có một flash trắng thay thế.

Sau 45 phút và 6 lần thử, developer tự tìm ra nguyên nhân: một cuộc gọi localStorage.getItem() đang xảy ra sau lần paint đầu tiên, gây ra double-render. Bản fix là 3 dòng code.

Tại sao agent thất bại? Không phải model. Claude Opus có thể suy luận về race condition. Vấn đề là agent đang đoán mò mà không có bằng chứng. Nó không thể thấy trang đã render. Nó không thể thấy browser console. Nó không thể quan sát hành vi paint thực tế.

Nó đang debug trong bóng tối.


Những Điểm Mù Của Agent

Coding agent hoạt động trong terminal có ba khoảng trống tầm nhìn cơ bản:

Điểm Mù 1: Giao Diện Đã Render

Agent có thể đọc file HTML. Nó không thể thấy browser render gì từ HTML đó.

Khoảng cách lớn hơn về trường. Browser không chỉ parse HTML — nó tính toán CSS specificity, áp dụng animation, evaluate JavaScript thay đổi DOM, xử lý z-index stacking. Một element trong source HTML có thể vô hình vì element khác có z-index: 9999 phủ lên trên. Agent không thể biết mà không nhìn thấy render.

Chụp màn hình giúp nhưng không giải quyết hoàn toàn. Screenshot cho agent hình ảnh nhưng không có element ID, computed style, ARIA role. “Nút trông có vẻ sai” là dạng vấn đề khác để debug so với “pointer-events: none vì ancestor có overflow: hidden với z-index sai.”

Điểm Mù 2: Runtime Logs

Khi code chạy trong production, agent không thể thấy điều gì đang xảy ra trừ khi bạn kết nối log cho nó.

Một Cloudflare Worker thất bại im lặng sau 10ms vì lỗi encoding ký tự — agent không thể biết. Nó có thể đọc source code. Nó không thể quan sát việc thực thi.

Đây là lý do lỗi Cloudflare Workers 1101 khó debug mà không có bằng chứng. Worker đang nhận markdown, xử lý nó, và sau đó — tại một điểm nào đó — thất bại. Lỗi xuất hiện trong dashboard Cloudflare nhưng không phải trong source code.

Điểm Mù 3: Cấu Trúc Ngữ Nghĩa

Ngay cả trong code mà nó có thể đọc, agent có hiểu biết ngữ nghĩa hạn chế. Nó đọc văn bản. Nó không hiểu ý nghĩa.


Bộ Công Cụ Evidence-Based

Giải pháp là cho agent bằng chứng — quan sát thực tế của hệ thống — không chỉ là source code.

Đây là các công cụ tôi dùng:

Chrome DevTools MCP

Google ra mắt Chrome DevTools MCP vào ngày 22/9/2025. Nó cho AI coding agent truy cập trực tiếp vào Chrome DevTools Protocol.

Ý nghĩa thực tế: agent giờ có thể quan sát trình duyệt thay vì đoán về nó.

Agent có thể làm gì với DevTools MCP:

DOM inspection:

agent: "z-index tính toán của element #modal-overlay là gì?"
devtools: "Element #modal-overlay: z-index tính toán là 'auto' (không phải 9999 như trong source).
           Parent .modal-wrapper có overflow: hidden đặt lại stacking context.
           Fix: chuyển modal-overlay ra ngoài .modal-wrapper trong DOM."

Style debugging:

agent: "Tại sao dark mode toggle không áp dụng class dark?"
devtools: "Event listener trên #dark-toggle đang fire đúng.
           document.documentElement.classList.add('dark') ĐANG được gọi.
           Nhưng MutationObserver trên .theme-wrapper đang xóa class 'dark' 12ms sau.
           Stack trace: theme.js:47 -> initThemeObserver -> removeAttribute"

Tôi dùng Chrome DevTools MCP để debug reading mode của blog. Highlight sync bị lệch một đoạn. Với DevTools, agent có thể quan sát giá trị scrollY, bounding box của từng đoạn, và trạng thái CSS highlight đồng thời. Fix trong 8 phút. Không có DevTools: ước tính hơn 2 giờ đoán mò.

Docker Logs MCP

Cho debug phía server, Docker Logs MCP cho agent truy cập stdout/stderr của container theo thời gian thực.

Ví dụ thực tế: Nâng cấp TTS caching đêm qua. Sau khi deploy, tôi muốn xác minh Cloudflare Cache API đang thực sự cache. Agent:

  1. Gửi request TTS cho cụm từ thử nghiệm
  2. Kiểm tra header X-TTS-Cache (MISS lần đầu)
  3. Gửi cùng request đó lần nữa
  4. Kiểm tra header (HIT — chứng minh edge cache hoạt động)
  5. Kiểm tra Wrangler logs để xác nhận function không chạy lần thứ hai

Không có log access, chúng tôi phải tin vào header. Với log, chúng tôi xác nhận cache hoạt động ở cấp độ hạ tầng.

Serena MCP — Hiểu Ngữ Nghĩa Code

Serena là MCP server mã nguồn mở cung cấp hiểu biết ngữ nghĩa cấp IDE về code. Nó không phải text search — mà là symbol resolution thực sự.

Điều này thay đổi gì:

Không có Serena:

agent: grep tìm "getLessonById"
kết quả: 23 match trên 12 file
agent: đọc tất cả 12 file
agent: hết context
agent: đoán giáo dục về match nào liên quan

Với Serena:

agent: "Tìm tất cả caller của getLessonById và kiểu trả về kỳ vọng"
serena: "getLessonById được gọi từ 3 nơi:
         1. apps/web/src/app/[locale]/lessons/[id]/page.tsx:47
            -- kỳ vọng: Lesson | null (xử lý null với redirect)
         2. apps/web/src/components/LessonCard.tsx:23
            -- kỳ vọng: Lesson (không có null check -- có thể crash nếu thiếu lesson)
         3. apps/web/src/api/lesson-export.ts:89
            -- kỳ vọng: Lesson[] (SAI KIỂU -- sẽ thất bại im lặng)"

Serena tìm ra type mismatch trong lesson-export.ts mà grep không bao giờ tìm được — vì nó hiểu kiểu, không chỉ văn bản.

GitNexus — Đồ Thị Tri Thức Codebase

GitNexus index repository thành đồ thị tri thức: node là code entity (hàm, class, interface), edge là quan hệ (gọi, import, kế thừa).

Use case chính: phân tích bán kính ảnh hưởng. Trước khi thay đổi hàm chia sẻ, agent hỏi GitNexus: “Nếu tôi thay đổi kiểu trả về của synthesizeChunk(), thứ gì bị hỏng?”

GitNexus bán kính ảnh hưởng: synthesizeChunk()

Caller trực tiếp: onRequestPost() trong tts.ts
Caller gián tiếp: không có (edge function, không có import)

Tác động: THẤP -- thay đổi bị gói gọn trong file này.
An toàn để sửa kiểu trả về.

Một Phiên Debug Evidence-Based Đầy Đủ

Đây là debug session với đầy đủ bằng chứng.

Vấn đề: TTS bài blog tiếng Việt đang phát tiếng Anh dù đang ở đường dẫn /vi/.

Không có bằng chứng:

  • Agent sẽ tìm kiếm code phát hiện ngôn ngữ
  • Tìm thấy nhiều ứng viên
  • Đoán cái nào sai
  • Thay đổi
  • Hy vọng nó hoạt động

Với bằng chứng:

  1. DevTools MCP: Agent kiểm tra giá trị document.documentElement.lang trên trang VI

    • Kết quả: "en" — thuộc tính lang là tiếng Anh dù URL là VI
  2. Điều tra source: Agent đọc BaseLayout.astro

    • Tìm thấy: lang={locale} được đặt đúng — HTML element sẽ có lang="vi"
  3. DevTools lần nữa: Agent kiểm tra xem trang đã hoàn tất hydrate chưa

    • Tìm thấy: lang ban đầu là "en" rồi đổi thành "vi" sau hydration — race condition
  4. Nguyên nhân gốc: TTSPlayer.astro khởi tạo trước Astro View Transitions framework cập nhật thuộc tính lang

  5. Fix: Đọc document.documentElement.lang tại thời điểm TTS được trigger (người dùng click play) thay vì lúc khởi tạo

  6. Xác nhận: DevTools xác nhận lang"vi" lúc click play — fix chính xác

Tổng thời gian: 11 phút. Không đoán mò. Mỗi bước có căn cứ trong quan sát thực tế.


Nguyên Tắc: Đo Lường, Rồi Mới Fix

Quy tắc cho debug dựa trên bằng chứng rất đơn giản:

Trước khi đề xuất fix, quan sát vấn đề.

  1. Giá trị thực tế của trạng thái có vẻ sai là gì?
  2. Giá trị đó đang được đặt ở đâu?
  3. Nó nên được đặt là gì?
  4. Khi nào giá trị sai xuất hiện?

Bốn câu hỏi. Bốn quan sát. Một bản fix.

Agent trả lời bốn câu hỏi này bằng đo lường thực tế sẽ fix bug chính xác ngay lần đầu. Agent đoán mò sẽ cần sau lần.

Hãy cho agent mắt để nhìn. Công cụ tồn tại. Hãy dùng chúng.


Đây là Phần 4 của series “Xây Bằng Agentic Engineering”. Trước: Agent QA Tiếp theo: Từ Agent Đến Production — Pipeline Deploy Đầy Đủ

Xuất nội dung

Bình luận