마지막 업데이트 2026-07-29
bugs/ 문서에 링크만 걸어 이 문서가 길어지지 않게 한다.
| 증상 | 사용자가 보는 현상 한 줄 |
| 원인 패턴 | 다른 코드에도 적용되는 일반화. 특정 파일 이야기로 쓰지 않는다 |
| 판별법 | 로그·쿼리·지표 시그니처. 재현 없이 확진할 수 있는 근거 |
| 적용 규칙 | 다음에 코드 쓸 때 지킬 것. 명령형으로 |
원인 패턴이 이미 있는 항목과 같으면 새 항목을 만들지 말고 기존 항목에 사례 링크만 추가한다 — 반복 횟수가 곧 우선순위 신호가 된다.
| 증상 | 진행자가 켠 '듣기'가 활동 전환 후 켜지지 않음 (PPI-1167) |
| 원인 패턴 | 같은 의미의 값이 두 곳에 있고, 자주 쓰는 쪽을 SSOT로 착각. 실제 정본은 그 값의 수명이 더 긴 쪽이었다. 사본은 리셋 경로(세션 종료·스텝 전환·차단 분기)를 갖고 있었다 |
| 판별법 | "판정하는 로그는 찍히는데 적용 로그가 없다" — 예약 실행(Defer mic: applied ...)은 있고 적용(Agent listening enabled)이 없으면 판정 입력이 빈손 |
| 적용 규칙 | 값을 읽어서 분기하는 코드를 새로 넣을 때, 그 값을 false/null로 되돌리는 경로를 먼저 전부 찾는다. 리셋 경로가 있으면 정본은 다른 곳이다. 하위 훅이 상위 상태를 판정에 쓰면 인자로 받는다 |
| 증상 | 기능은 동작하는데 특정 전환 뒤에만 조용히 no-op (PPI-1167) |
| 원인 패턴 | "무조건 실행"을 "값을 보고 판단"으로 바꾸면서 그 값의 공급 경로를 만들지 않음. 값이 비는 순간이 있는데 채워주는 주체가 없어 구조적으로 미완성 |
| 판별법 | 변경 전후 diff에서 - doSomething() → + if (x) doSomething() 형태를 찾고, x에 값을 쓰는 코드가 그 시점 이후에 존재하는지 역추적 |
| 적용 규칙 | 조건을 추가하면 그 조건의 공급자·수명·리셋을 같은 PR에서 명시한다. 기존 무조건 동작을 조건부로 바꿀 때는 조건이 false가 되는 경우를 테스트나 로그로 남긴다 |
| 증상 | 신규 기능은 정상인데 기존 동작이 깨져 revert (PPI-1167 / PPI-907 계열) |
| 원인 패턴 | 새 축(fallback)을 추가하면서 기존 축(configured)의 경로를 함께 바꿨는데, 검증은 새 축의 대표 시나리오만 수행. 새 축이 꺼져 있어야 정상인 상황이 사각지대 |
| 판별법 | 기능 조건을 나열해 "기능이 비활성인 조합"을 명시적으로 만든다. 예: 진행자 부재 fallback이면 진행자가 있는 수업이 사각지대 |
| 적용 규칙 | 기능 검증 케이스에 "기능 OFF 상태에서 기존 동작 유지"를 항상 포함한다. 새 축과 기존 축이 같은 적용 지점을 공유하면 두 축의 4조합(on/on, on/off, off/on, off/off)을 확인한다 |
| 증상 | 진행자 presence가 실제와 반대로 고정되어 다음 변경까지 유지 (PPI-1167 재랜딩 중 발견) |
| 원인 패턴 | snapshot(조회 응답)과 push(변경 이벤트)를 같은 상태에 적용하는데, 버전 비교를 한쪽에만 적용. 게다가 상태를 계산한 뒤 버전을 읽어 낡은 상태에 최신 버전이 붙어 stale이 최신으로 고정됐다. @socket.io/redis-adapter에서 cross-instance push는 pub/sub을 거쳐 로컬 ack보다 늦게 도착할 수 있다 |
| 판별법 | 드롭 지점에 로그를 심어 source·수신 버전·적용 버전을 함께 남긴다. 정상 동작 중에는 이 로그가 뜨지 않아야 한다 |
| 적용 규칙 | 버전 비교는 모든 소스에 동일하게 적용한다. 서버는 버전을 상태 계산 전에 캡처해 "상태 ≥ 버전" 불변식을 만든다(반대면 낡은 상태가 최신으로 위장). 버전 공간이 리셋될 수 있으면(Redis 재기동) 연결 단위로 baseline을 비운다 |