---
name: feedback_root_cause_over_symptom
description: Bug lặp sau band-aid → truy field/case LỆCH-quy-ước vào pattern đã-đúng = fix 1 chỗ; +S122 mở-rộng 2 trục — (i) vá-cái-được-CHỈ mà không QUÉT-cùng-lớp, (ii) bài-học-trong-memory KHÔNG tự bật, anh phải chỉ
metadata: 
  node_type: memory
  type: feedback
  originSessionId: f9d8869d-32be-4115-9cc9-dadc5343b375
---

Bug TÁI-DIỄN sau khi đã "vá điểm" (SQL UPDATE sửa data, tzutil đổi giờ máy-chủ, gán-tay từng record) = tín-hiệu MẠNH root-cause còn sống ở tầng code/serialize, KHÔNG phải data point-in-time.

**Why:** S85 anh Kiệt FDC UAT — 2 bug whack-a-mole: (1) phiếu mất Quy trình duyệt → SQL-fix 5 phiếu xong VẪN đẻ phiếu workflow-less MỚI (A/005 14:54, A/010 15:08 tạo SAU fix) → systemic chứ không phải data cũ; root = `UpdatePeDraftHandler` absolute-set `ApprovalWorkflowId=null` khi form Sửa omit field (1 field SÓT khỏi pattern null-safe mà field cạnh BudgetPeriodAmount/WorkItemId đã có — bug-class S42, gotcha #73). (2) "báo 7h trước" → tzutil đổi-giờ-VPS là band-aid (ảnh-hưởng GETUTCDATE, đẻ lệch khác); root = DateTime serialize thiếu 'Z' (EF Unspecified-kind) → global `UtcDateTimeJsonConverter` ép UTC-with-Z (gotcha #72). Cả 2: fix-điểm che triệu-chứng nhưng nguồn ở tầng dưới → tái-diễn cho tới khi truy gốc.

**How to apply:** Bug lặp sau 1 fix → DỪNG vá-data, hỏi "CASE nào tạo ra trạng-thái lỗi NÀY?" → truy tới handler/serializer/validator. Nghi đặc-biệt: (a) field LỆCH khỏi pattern-đồng-cấp (1 field absolute-set giữa N field null-safe); (b) tầng-config global (TZ/JSON/culture) thay vì per-record. Verify root-fix bằng "tạo trạng-thái lỗi MỚI có còn lặp không", KHÔNG chỉ "data cũ đã sạch chưa". SQL-fix vẫn OK làm BRIDGE tạm trong lúc deploy root-fix — nhưng phải biết nó là bridge. Liên-hệ [[feedback_agent_kill_recovery]] (self-gate khi reviewer chết), [[feedback_realdata_master_import]], gotcha #72/#73.

---

## 🔴 MỞ-RỘNG S122 (2026-07-15) — anh nêu; bài-học NÀY đã ở trong context mà vẫn KHÔNG tự bật

**Ca:** lead vá hardcode `$userMemDir = 'C:\Users\pqhuy\...\memory'` trong `governance-detectors.ps1` → **coi như xong**. **Anh chỉ ra gốc:** *"UserMem phải ghi trong thư-mục dự-án chứ ko phải ổ C — cái này lâu lâu drift dính mãi"*. Đo ra: user-memory **48 file · 151 671 B** ở nơi **0 git · 0 Dropbox · 0 history**, trong khi `.claude/agent-memory/` 1.1MB **git đủ** ⇒ bất-đối-xứng không lý-do kỹ-thuật; và **bug #53 zero-byte Write (S45 MẤT index thật) xảy ra ĐÚNG ở thư-mục không có history để phục-hồi** ⇒ *"verify byte>0"* = **kỷ-luật người đứng thay version-control**. Hardcode chỉ là **hệ-quả** của vị-trí-ngoài-repo.

**2 trục MỚI (khác trục cũ "band-aid data vs root code"):**

**(i) 🔴 Vá-cái-được-CHỈ mà KHÔNG QUÉT-cùng-lớp.** WAL nêu đích-danh **1** script; lead vá đúng nó rồi **DỪNG**. Anh hỏi mới lòi ra **script THỨ HAI cũng đọc user-memory** (`spawn-model-audit.ps1` — **may là sạch sẵn**). ⇒ **thoát vì MAY, không vì quy-trình.** Sửa 1 instance ≠ đóng 1 CLASS. *(Cùng họ [[feedback_cardinality_change_grep_consumers]] nhưng khác đầu vào: ở đó là "đổi field → grep consumer"; ở đây là **"được chỉ 1 chỗ → grep xem còn chỗ nào cùng lớp"** — kể cả khi không ai bảo có chỗ khác.)*

**(ii) 🔴 Bài-học nằm-trong-memory KHÔNG tự bật.** File NÀY được auto-inject đầu phiên S122. Lead **đọc rồi vẫn vá triệu-chứng**. ⇒ **nạp bài-học vào context ≠ có phòng-thủ.** Cùng phiên còn 1 chứng nữa: lead ghi **SHA BỊA** vào WAL trước khi commit = tái-phạm **y nguyên** bài-học *"khẳng-định fact-trên-đĩa mà không đo đĩa"* đã ghi **1 NGÀY trước**. ⇒ thứ chặn được = **LUẬT/cổng soi được**, không phải trí-nhớ.

**Bằng-chứng "biết cách đúng" KHÔNG đủ (đo, không suy):** hardcode sinh `e70c046` **2026-06-18** → vá `c39ba9d` **2026-07-15** = **sống 27 NGÀY**. Cơ-chế **KHÔNG phải tái-phạm**: `git log -S` ra 8 commit nhưng **5/8 `ps1/js-touched=0`** (chỉ doc **nhắc** path) — nó **sinh 1 lần rồi SỐNG SÓT qua rà**, và 1 trong 3 lần chạm code thật là **`ae957c4` "double-check ×2 + finalize"** = **đợt double-check sờ đúng file mà không bắt**. Vì sao double-check trượt: nó hỏi *"logic đúng không?"* — mà hardcode thì **logic VẪN ĐÚNG**, chỉ sai **tính di-động**. 🟢 Đối-chứng: `spawn-model-audit.ps1` (viết **cùng ngày**) làm chuẩn sẵn + tự ghi *"NOT a hardcoded absolute path"*.

**How to apply (bổ-sung):**
1. **Được chỉ 1 chỗ ⇒ HỎI "còn chỗ nào CÙNG LỚP?" rồi GREP** — trước khi báo xong. `grep -rn 'C:\\Users\|/home/\|/Users/' scripts/` là 1 lệnh.
2. **Vá xong ⇒ hỏi tiếp "vì sao HÌNH-DẠNG này tồn-tại?"** — hardcode tồn-tại **VÌ** path nằm ngoài repo. Vá hardcode = vá lá; hỏi vị-trí = chạm gốc.
3. **Đề-xuất LUẬT, đừng đề-xuất "nhớ cẩn-thận hơn"** — 27 ngày + 1 double-check đã chứng minh trí-nhớ không đỡ được.
4. **Giới-hạn quyền:** gốc nằm ở **harness** (`settings.json` project+user = **0 key** memory-path) ⇒ **KHÔNG tự dời** (dời tay ⇒ harness thôi auto-inject ⇒ **mất memory khỏi context** = hỏng nặng hơn) ⇒ **hỏi hub** → `adap-requests/2026-07-15-se-r6-user-memory-outside-repo-no-backup.md`. Truy gốc **không** đồng nghĩa tự-sửa-gốc.

🔸 **Trớ trêu tự-chỉ:** chính file này — cùng 47 file bài-học khác — đang nằm ở thư-mục **không có backup** mà nó vừa mô-tả. Xem [[feedback_session_end_memory_write_verify]] (bug #53 zero-byte, verify byte>0).
