에이전트에게 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와 무관한 공실패로 먼저 거릅니다. 재시도 통과만으로 “테스트 탓”이라고 단정하지 않습니다. 인프라 실패도 재시도에 통과하기 때문입니다.

거르는 순서(프로세스):

  1. 재시도는 감지 신호로만 씁니다. 실패 → 같은 커밋·같은 job 재실행 → 통과면 “가능 flake/인프라” 후보입니다. 조용히 초록으로만 남기지 말고 시도 횟수·각 시도 결과·커밋 SHA·job URL을 남깁니다.
  2. 격리(quarantine) 상태를 확인합니다. Fowler가 말한 대로 non-deterministic 테스트는 건강한 스위트와 분리해야 합니다. 이미 quarantine/skip/known-flake 목록에 있고 실패 모드가 기록과 같으면 새 재현 작업이 아니라 기존 격리 프로세스로 넘깁니다.
  3. 환경·인프라 사유를 먼저 뺍니다. 러너 이미지·컨테이너 태그·캐시·네트워크·디스크·권한 오류처럼 제품 테스트 전에 깨진 로그면 에이전트에게 “프로덕트 버그 재현”을 시키지 않습니다. CI 요약의 reason/tag가 있으면 그 경로로 라우팅합니다.
  4. 이력 창을 봅니다. 최근 N회에서 간헐 실패인지, 특정 날짜부터 계단식으로 깨졌는지, 한 PR 근처에서만 연속 실패인지 로그·대시보드 이력으로 구분합니다. “요즘 자주 깨짐”만으로 숫자를 지어내지 않습니다.
  5. 공실패(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 · 시도 이력이 들어가야 합니다. “가끔 실패함”은 증거가 아닙니다.

증거 체크리스트:

  1. 판정 한 줄. deterministic-defect | flake-candidate | infra/env | quarantined-known 중 하나. 애매하면 unclassified로 두고 부족한 신호(이력·공실패·환경)를 적습니다.
  2. 필터 근거. 재시도 결과, quarantine 매칭 여부, CI reason/tag, 이력 창에서 본 패턴(간헐 vs 계단식), diff 근접 여부.
  3. 재현 명령 그대로. 위에서 쓴 최소 세트 복붙. 에이전트가 “비슷한 명령”으로 바꾸지 않게 합니다.
  4. 1회 실패 산출물. 로그 경로, assertion/스택 요약, (있으면) JUnit fail 노드. 통과한 재시도 로그도 나란히 남기면 flake 판정에 도움이 됩니다.
  5. 식별자. 커밋 SHA, CI job/run URL, 테스트 full name, 러너 이미지/태그(환경 델타용).
  6. 다음 액션. 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(실패 테스트부터)