에이전트에게 flake가 아닌 실패만 재현하게 하려면?
CI가 빨개지면 에이전트에게 “실패 재현해서 고쳐”라고 넘기기 쉽습니다. 그런데 그 실패가 같은 커밋에서 재시도하면 통과하거나, 격리·시간·원격·리소스에 흔들리면 에이전트는 존재하지 않는 회귀를 쫓거나, flake를 “고친 척” 타임아웃만 올립니다. 에이전트 실패 재현 flake 문제는 “테스트 더 돌리기”가 아니라 재시도성 신호를 거른 뒤 deterministic 실패만 재현·보고하게 프롬프트하는 절차입니다.
이 글은 **flake 신호는 어떻게 거르나? · 재현 명령 최소 세트는? · 리포트에 넣을 증거는?**만 다룹니다. agent-test-gate는 턴 직후 게이트, agent-tdd-loop는 실패 테스트부터 고치기입니다. 여기서는 재현 요청 전에 flake를 빼고 증거만 남기는 축입니다. 요금·제휴·C++ 예제·발명 통과율은 없습니다.
근거는 Martin Fowler — Eradicating Non-Determinism in Tests의 **quarantine·원인 축(고립·비동기·원격·시간·리소스 누수)**과, CI에서 쓰는 재시도·이력·공실패·환경 델타·격리 목록 같은 프로세스·로그 휴리스틱입니다. 수치 통과율은 팀·도구마다 다르므로 숫자를 발명하지 않고 “무엇을 기록·비교할지”만 적습니다.
flake 신호는 어떻게 거르나?
한 줄 답: flake 후보는 같은 SHA에서 재시도 통과 · 격리 목록 매칭 · 환경/인프라 사유 · PR diff와 무관한 공실패로 먼저 거릅니다. 재시도 통과만으로 “테스트 탓”이라고 단정하지 않습니다. 인프라 실패도 재시도에 통과하기 때문입니다.
거르는 순서(프로세스):
- 재시도는 감지 신호로만 씁니다. 실패 → 같은 커밋·같은 job 재실행 → 통과면 “가능 flake/인프라” 후보입니다. 조용히 초록으로만 남기지 말고 시도 횟수·각 시도 결과·커밋 SHA·job URL을 남깁니다.
- 격리(quarantine) 상태를 확인합니다. Fowler가 말한 대로 non-deterministic 테스트는 건강한 스위트와 분리해야 합니다. 이미 quarantine/skip/known-flake 목록에 있고 실패 모드가 기록과 같으면 새 재현 작업이 아니라 기존 격리 프로세스로 넘깁니다.
- 환경·인프라 사유를 먼저 뺍니다. 러너 이미지·컨테이너 태그·캐시·네트워크·디스크·권한 오류처럼 제품 테스트 전에 깨진 로그면 에이전트에게 “프로덕트 버그 재현”을 시키지 않습니다. CI 요약의 reason/tag가 있으면 그 경로로 라우팅합니다.
- 이력 창을 봅니다. 최근 N회에서 간헐 실패인지, 특정 날짜부터 계단식으로 깨졌는지, 한 PR 근처에서만 연속 실패인지 로그·대시보드 이력으로 구분합니다. “요즘 자주 깨짐”만으로 숫자를 지어내지 않습니다.
- 공실패(co-failure)와 변경 근접을 같이 봅니다. 같은 shard/스위트에서 여러 테스트가 동시에 깨지면 공유 상태·픽스처 후보입니다. 호출 그래프에 가까운 파일이 이번 변경에 있으면 회귀 후보로 올립니다. diff와 무관한 단일 간헐 실패는 flake 쪽으로 기울입니다.
에이전트 프롬프트에 넣을 한 줄:
Do not chase failures that: (a) pass on retry of the same SHA,
(b) match quarantine/known-flake list, (c) show infra/env reason
before product asserts, (d) have no change-set proximity and a
history of intermittent fails. Only reproduce deterministic product
failures. Record why each red was filtered.
재현 명령 최소 세트는?
한 줄 답: 최소 세트는 실패한 테스트(또는 job)만 · 시드/환경 고정 · 재시도 없이 1회 · 로그 경로 고정입니다. “전체 스위트 여러 번”은 재현이 아니라 노이즈 증폭입니다.
최소 명령 뼈대(프레임워크 이름은 팀 도구로 바꿉니다):
| 단계 | 목적 | 통과 기준 |
|---|---|---|
| 1. 대상 확정 | CI 로그의 실패한 테스트 이름·파일·job | 한 줄로 식별 가능 |
| 2. SHA 고정 | git rev-parse HEAD = CI 실패 커밋 | 다른 브랜치/dirty 금지 |
| 3. 단일 실행 | 해당 테스트만, retry 끔 | exit non-zero면 그대로 기록 |
| 4. 시드·시간 | random/seed·clock stub·TZ 고정(가능하면) | Fowler의 time/async 축 |
| 5. 격리 환경 | 로컬 캐시·병렬 워커 최소화 | 공유 DB/포트 충돌 제거 |
| 6. 산출물 | stdout/stderr·JUnit/XML·artifacts 경로 | 리포트에 붙일 파일 존재 |
프롬프트용 최소 세트 예시:
Repro (deterministic only):
1) cd <repo> && git checkout <failing-sha> && git status --porcelain # must be empty
2) export TZ=UTC CI=1 <SEED_ENV>=<fixed> # no live clock drift if wrap exists
3) <test-runner> --no-retry path/to/test::CaseName
4) On non-zero: save full log to artifacts/repro-<sha>-<case>.log
5) Do NOT: bump timeouts, add sleeps, re-run until green, edit quarantine list
6) If second identical command passes without code change → stop; label flake-candidate; do not "fix"
하지 말 것: bare sleep으로 비동기 기다리기(Fowler), 원격 실서비스에 붙여 비결정성 키우기, quarantine에 넣고 잊기, “N번 중 M번 통과”처럼 발명한 통과율을 리포트에 쓰기.
리포트에 넣을 증거는?
한 줄 답: 리포트에는 판정( flake-candidate / infra / deterministic-defect ) · 거른 이유 · 재현 명령 전문 · 1회 실패 로그 링크 · SHA·job URL · 시도 이력이 들어가야 합니다. “가끔 실패함”은 증거가 아닙니다.
증거 체크리스트:
- 판정 한 줄.
deterministic-defect|flake-candidate|infra/env|quarantined-known중 하나. 애매하면unclassified로 두고 부족한 신호(이력·공실패·환경)를 적습니다. - 필터 근거. 재시도 결과, quarantine 매칭 여부, CI reason/tag, 이력 창에서 본 패턴(간헐 vs 계단식), diff 근접 여부.
- 재현 명령 그대로. 위에서 쓴 최소 세트 복붙. 에이전트가 “비슷한 명령”으로 바꾸지 않게 합니다.
- 1회 실패 산출물. 로그 경로, assertion/스택 요약, (있으면) JUnit fail 노드. 통과한 재시도 로그도 나란히 남기면 flake 판정에 도움이 됩니다.
- 식별자. 커밋 SHA, CI job/run URL, 테스트 full name, 러너 이미지/태그(환경 델타용).
- 다음 액션. deterministic이면 수정 범위; flake-candidate면 quarantine+티켓+기한(Fowler: 격리하되 빨리 고칠 것); infra면 플랫폼 이슈. 고친 척 타임아웃 상향 금지.
리포트 스케치:
Verdict: deterministic-defect
Filtered out: none (same SHA retry still failed; not on quarantine; assert in product code)
SHA: abcdef1
Job: https://ci.example/jobs/12345
Repro:
git checkout abcdef1
<test-runner> --no-retry tests/foo::test_bar
Evidence: artifacts/repro-abcdef1-test_bar.log (assertion at foo.py:88)
Next: fix product path X; do not quarantine
한 줄 정리: 에이전트 재현 = flake/인프라 신호 필터 → 최소 deterministic 명령 1회 → 판정·명령·로그·SHA를 증거로 보고. 게이트(agent-test-gate)·TDD 루프(agent-tdd-loop)와 맞물되, 이 글은 재현 요청의 flake를 빼는 앞단입니다.
FAQ
재시도에 통과하면 무조건 flake인가요?
아닙니다. 인프라·환경 실패도 재시도에 통과합니다. CI reason/tag·러너/이미지 델타·공실패를 본 뒤, 제품 assert 경로가 흔들릴 때만 flake-candidate로 올립니다.
quarantine에 넣으면 끝나나요?
아닙니다. Fowler는 격리로 건강한 스위트를 지키되, 격리 테스트는 빨리 고칠 작업이라고 합니다. 에이전트 프롬프트에 “막히면 quarantine”을 기본으로 넣지 않습니다. 목록·소유자·기한이 있을 때만 그 프로세스로 넘깁니다.
agent-test-gate와 무엇이 다른가요?
agent-test-gate는 에이전트 출력 직후 테스트 명령으로 통과/중단하는 게이트입니다. 이 글은 빨간 CI를 에이전트에 넘기기 전에 flake·인프라를 거르고, 재현 범위와 증거를 고정하는 쪽입니다.
통과율을 리포트에 써야 하나요?
팀 대시보드에 이미 있는 수치만 인용합니다. 발명·추정 통과율은 쓰지 않습니다. 없으면 시도 횟수·각 결과·날짜 구간만 적습니다.
출처 (Sources)
- Martin Fowler — Eradicating Non-Determinism in Tests — quarantine, isolation / async / remote / time / resource leaks
- CI 프로세스 휴리스틱: 재시도를 감지 신호로 기록, 이력 창·공실패·환경 델타·격리 목록으로 판정 (팀 CI 로그·요약 reason 기준)
- 인접 축: agent-test-gate(턴 직후 게이트), agent-tdd-loop(실패 테스트부터)