Điểm nguy hiểm của vibe coding không phải là app không chạy. Ngược lại, app thường chạy rất nhanh. Form hiện ra, nút bấm được, dashboard có số, màn hình nhìn giống thứ mình yêu cầu. Cái khó nằm ở phần sau: code bên dưới có đang đổi quá nhiều không, có thêm dependency lạ không, có để lộ secret không, có động vào database hoặc quyền truy cập mà mình không nhận ra không.

Với prototype nhỏ, bạn có thể review bằng mắt và click thử flow chính. Nhưng từ lúc app bắt đầu có login, dữ liệu thật, file upload, thanh toán, admin view, hoặc database migration, review UI là chưa đủ. Bạn phải nhìn diff. Không cần trở thành senior engineer ngay lập tức, nhưng cần biết chỗ nào phải dừng lại và gọi người có kinh nghiệm.

Bài này không thay thế code review chuyên nghiệp. Nó là checklist để người dùng vibe coding không tự lừa mình rằng “AI nói xong rồi” nghĩa là code đã an toàn.

Bắt đầu từ file list, không bắt đầu từ màn hình đẹp

Khi agent sửa xong, việc đầu tiên không phải hỏi “có chạy không”. Việc đầu tiên là xem nó đã chạm vào đâu.

Nếu tool có Git integration, mở tab changed files. Nếu làm local repo, chạy:

git status --short
git diff --stat

Bạn muốn thấy blast radius. Ví dụ bạn yêu cầu “thêm validation cho booking form”. Diff hợp lý có thể chạm vào component form, schema validation, vài test hoặc copy error. Diff đáng nghi là chạm cả auth config, database client, dependency lockfile, route admin, và file deploy.

Không phải cứ nhiều file là sai. Nhưng nhiều file phải có lý do. Vibe coding hay tạo cảm giác mọi thứ liền mạch: bạn nói một câu, agent tự sửa toàn bộ app. Chính vì vậy file list là cái phanh đầu tiên. Nó cho bạn biết task nhỏ có đang biến thành rewrite không.

Một câu hỏi đơn giản:

Thay đổi này có nằm trong phạm vi tôi yêu cầu không?

Nếu câu trả lời là “không chắc”, chưa nên accept.

Đọc dependency mới như đọc hợp đồng nhỏ

Dependency mới là một dạng nợ. Agent thêm package để làm date picker, export CSV, upload ảnh, parse PDF, gửi email, validate form. Có package là đúng. Nhưng mỗi dependency mới kéo theo version, security risk, bundle size, license, maintenance, và đôi khi behavior khó debug.

Khi thấy package.json, package-lock.json, pnpm-lock.yaml, hoặc yarn.lock đổi, hãy hỏi:

  • Dependency này dùng để làm gì?
  • Project đã có package tương tự chưa?
  • Có thể làm bằng API sẵn có hoặc helper hiện có không?
  • Package có đang bị dùng ở đúng một chỗ rất nhỏ không?
  • Nó có chạy code phía server, đụng file system, network, hoặc credential không?

Với non-tech user, câu prompt thực dụng là:

Explain every new dependency you added.
For each one, say why existing code cannot do the job.
Do not change code yet.

Nếu agent trả lời vòng vo kiểu “for better robustness” nhưng không chỉ ra file đang dùng, đó là tín hiệu yếu. Dependency không cần văn hay. Nó cần lý do cụ thể.

Tìm secret, .env, API key và log

Vibe-coded app rất dễ đi từ demo sang dữ liệu thật: Stripe key, email API key, Google Sheets token, database URL, webhook secret. Đừng dán secret vào chat nếu có lựa chọn khác. Đừng để agent ghi secret vào source code. Đừng commit .env.local.

Scan diff cho các dấu hiệu:

API_KEY
SECRET
TOKEN
PASSWORD
DATABASE_URL
Authorization
Bearer
console.log

Một lỗi hay gặp: agent thêm log để debug request, rồi log luôn payload có email, phone, token hoặc cookie. Trong prototype local, bạn thấy vô hại. Khi deploy lên hosting, log đó có thể nằm trong dashboard, được team khác xem, hoặc bị gửi qua hệ thống logging.

Rule ngắn:

  • Secret chỉ nằm trong secret manager hoặc .env gitignored.
  • .env.example chỉ có placeholder.
  • Không hardcode token trong code.
  • Không log request headers, cookies, access token, password, database URL.
  • Không paste production secret vào prompt.

Nếu agent cần config, bắt nó viết hướng dẫn:

Use environment variables.
Update .env.example with placeholder names only.
Do not include real secrets in code or logs.

Database migration là vùng cần nghiêm túc

Database là nơi vibe coding dễ làm hỏng thật. UI sai có thể sửa. CSS xấu có thể sửa. Nhưng migration sai có thể mất data, đổi schema không rollback được, hoặc làm app cũ không đọc được dữ liệu mới.

Khi diff có file migration, schema, model, ORM config, hoặc query tạo bảng, hãy dừng lâu hơn. Các dấu hiệu cần dev review:

  • Xoá cột hoặc đổi tên cột có data.
  • Đổi type của cột đang dùng.
  • Thêm NOT NULL cho cột chưa có default.
  • Tạo relation mới nhưng không migration dữ liệu cũ.
  • Đổi enum value.
  • Thêm cascade delete.
  • Đụng permission hoặc row-level security.
  • Chạy seed script xoá data.

Với prototype chưa có user thật, migration có thể thoải mái hơn. Nhưng nếu đã có data thật, câu “rollback nếu sai” không còn đơn giản. Một số migration không rollback sạch. Một số rollback code vẫn để database ở trạng thái mới.

Prompt an toàn hơn:

Do not run migrations.
Explain the schema change first.
List data-loss risks and rollback plan.
Wait for approval before modifying database files.

Nếu tool đã tự chạy migration mà bạn không hiểu nó làm gì, đừng deploy tiếp. Lấy diff, lấy migration file, gọi dev review.

Permission change thường bị che trong code nhỏ

Một vibe-coded feature nhìn như UI nhưng thật ra thay đổi quyền. Ví dụ thêm admin page, export CSV, upload attachment, invite member, view booking list. Agent có thể làm nhanh bằng cách “cho mọi user đọc được”, hoặc bỏ check role để flow chạy.

Review các điểm này:

  • Route mới có yêu cầu login không?
  • API handler có check user ownership không?
  • Admin action có check role không?
  • File upload có giới hạn loại file và size không?
  • Export có lọc theo tenant/user không?
  • Client có đang tự quyết định quyền thay vì server không?

Một câu hỏi rất đáng dùng:

Show me every authorization check involved in this change.
If there is no auth check, explain why.

Nếu agent chỉ nói “the UI hides the button”, chưa đủ. Hiding button không phải security. Server vẫn phải kiểm tra quyền.

Network call và file upload cần được nhìn riêng

Network call mới có nghĩa app đang nói chuyện với bên ngoài. Có thể là analytics, payment, email, AI API, webhook, storage, map service. Mỗi call mới cần biết dữ liệu gì được gửi đi.

Đọc diff để tìm:

fetch(
axios
POST
webhook
upload
FormData
S3
Supabase storage
Cloudinary

Hỏi agent:

List all new outbound network calls.
For each call, show destination, payload fields, and error behavior.

File upload thì càng cần kỹ. Upload không chỉ là chọn file và preview. Cần giới hạn file size, type, storage path, public/private access, virus scan nếu production nghiêm túc, và cleanup khi user huỷ. Với vibe-coded MVP, tối thiểu phải có file size limit, allowed types, và không để file private thành public URL ngoài ý muốn.

Khi nào phải gọi dev review

Không phải thay đổi nào cũng cần dev review. Sửa copy, đổi layout nhẹ, thêm static page, prototype throwaway có thể tự xử. Nhưng có những vùng không nên tự tin quá.

Gọi dev review khi diff có:

  • Auth, roles, permission, admin.
  • Database migration hoặc schema change.
  • Payment, invoice, refund, booking inventory.
  • File upload.
  • Email/SMS gửi ra user thật.
  • Secret, API key, OAuth.
  • Background job, cron, queue.
  • Data export/import.
  • Dependency mới liên quan security hoặc server.
  • Deploy config, DNS, domain, environment variables.

Một review 20 phút trước khi share app public rẻ hơn nhiều so với một ngày dọn dữ liệu sai.

Prompt review thực dụng

Sau khi agent viết code, đừng chỉ hỏi “is it safe?”. Hỏi cụ thể:

Review your own diff.
Return:
1. Files changed grouped by purpose.
2. New dependencies and why they are needed.
3. New network calls and payloads.
4. Any secret/env changes.
5. Any database/schema/migration changes.
6. Any auth/permission changes.
7. Manual tests I should run.
8. Risks that need human dev review.
Do not modify code in this step.

Sau đó bạn vẫn đọc diff. Self-review của agent là bản đồ, không phải bằng chứng cuối cùng.

Chốt lại

Vibe coding làm app hiện ra rất nhanh, nhưng tốc độ đó làm mình dễ bỏ qua phần nền: dependency, secret, database, permission, network, upload. Nếu app chỉ là demo cá nhân, bạn có thể nhẹ tay. Nếu app có user thật hoặc data thật, diff review là bắt buộc.

Bạn không cần hiểu mọi dòng code để review có ích. Bạn cần biết chỗ nào nguy hiểm, biết hỏi câu cụ thể, và biết khi nào phải kéo dev thật vào. Một prototype tốt không chỉ là prototype chạy được. Nó là prototype mà người tiếp theo có thể đọc diff, hiểu rủi ro, và quyết định có nên đưa tiếp vào repo sống hay không.