0. Contents


1. Overview

1.1 승인에서 결과 표시까지

Kukie 앱 · 승인 카드: 눌러서 설명 보기Kukie 앱 · 승인 카드승인머지sync상태카드는 응답이 아니라 Plan 을 읽어 그린다CloudFront · ALB: 눌러서 설명 보기CloudFront · ALB응답 제한 60초넘으면 504GitHub 시험 레포: 눌러서 설명 보기GitHub 시험 레포PR #3 · 파일 지문main · base_sha머지 호출에 head_sha 를 함께 보낸다. 어긋나면 409클러스터 kukie-devweb: 눌러서 설명 보기webCaddy/api/* → agent:8000/member/* → spring:8080agent: 눌러서 설명 보기agent승인 요청 안방 잠금: 눌러서 설명 보기방 잠금처리 중 승인 · 재개는 409 BUSYrecheck(): 눌러서 설명 보기recheck()다르면 STALE, 머지 안 함merge(): 눌러서 설명 보기merge()브랜치 보호면 PR 을 남긴다기록 먼저: 눌러서 설명 보기4기록 먼저재시도는 기록을 보고 머지 생략응답: 눌러서 설명 보기응답보통 십몇 초에 200, 45초를 넘으면 202요청 밖응답과 떨어져 끝까지 돈다지켜보기 task: 눌러서 설명 보기지켜보기 taskApplication 을 읽어 머지 커밋의 sync 를 확인최대 10분. 동시 상한 32개8member: 눌러서 설명 보기memberstatus APPLIED: 눌러서 설명 보기status APPLIEDmerge_commit: 눌러서 설명 보기merge_commitsync_result: 눌러서 설명 보기sync_result15POST …/approve23PR · 파일 · main 조회head_sha 고정 머지9Plan 을 5초마다고객 클러스터 (시험)namespace argocdrepo-server: 눌러서 설명 보기6repo-server웹훅이 없으면 git 을 기본 3분마다 읽는다application-controller: 눌러서 설명 보기application-controller새 커밋을 렌더링해 클러스터에 맞춘다Application sandbox-plain: 눌러서 설명 보기Application sandbox-plainstatus.sync.revision = 머지 커밋status.sync.status = Syncedstatus.health.status = Healthy7kubectl 조회앱 sandbox-plainDeployment web: 눌러서 설명 보기Deployment webreplicas 4 → 2 (PR #3)Pod: 눌러서 설명 보기PodPod: 눌러서 설명 보기PodPod: 눌러서 설명 보기PodPod: 눌러서 설명 보기Pod실선 남는 Pod · 점선 줄어드는 Pod① ~ ⑤ 승인 요청은 머지와 기록까지 하고 답한다. ⑥ 은 ArgoCD 의 일이고, ⑦ ⑧ ⑨ 로 결과가 Plan 을 거쳐 카드에 나타난다STALE · 머지 실패 · 브랜치 보호는 ② ③ 에서 끝난다. 머지되지 않았으므로 ⑥ 으로 가지 않는다초록 승인 요청 안 · 보라 요청 밖 · 주황 ArgoCD · 회색 앱 · 웹 · 저장소 · 값은 시험 PR #3 의 것이다① 승인 요청 절로 이동① 승인 요청② 재확인 절로 이동② 재확인③ 머지 절로 이동③ 머지④ 기록 절로 이동④ 기록⑤ 응답 절로 이동⑤ 응답⑥ ArgoCD sync 절로 이동⑥ ArgoCD sync⑦ 지켜보기 절로 이동⑦ 지켜보기⑧ 결과 기록 절로 이동⑧ 결과 기록⑨ 카드 읽기 절로 이동⑨ 카드 읽기
승인 요청은 머지까지만 하고 답한다 — sync 결과는 뒤에서 Plan 에 쌓이고 카드가 읽는다
칸을 누르면 설명이 열린다

배경

  • 02편은 PR 이 열리는 데서 끝난다. 그 시점까지 클러스터에는 아무 변화가 없다
  • 승인 카드를 누르면 Kukie 가 PR 을 머지하고, ArgoCD 가 그 커밋을 sync 한다
  • 카드를 만든 뒤 PR 이나 레포가 바뀔 수 있고, sync 는 몇 분이 걸린다

이 글의 질문

  • 승인한 내용과 지금 내용이 같은지 어떻게 확인하는가
  • 끝나는 데 몇 분 걸리는 일을 HTTP 요청 하나로 어떻게 다루는가
  • 머지 직후 AI 답변이 나중에 틀린 말이 되는 문제를 어떻게 막는가

1.2 kubectl 승인과 PR 승인

항목kubectl 경로PR 경로
승인이 하는 일클러스터에 바로 적용PR 머지
클러스터 반영실행과 동시에ArgoCD sync 뒤
응답 시점의 결과끝난 결과머지까지만
되돌리기다시 kubectlrevert PR

이 차이가 5장의 장애와 6장의 답변 문제로 이어졌다.

2. Approval Card — 승인 카드

① 승인 요청 절로 이동① 승인 요청② 재확인 절로 이동② 재확인③ 머지 절로 이동③ 머지④ 기록 절로 이동④ 기록⑤ 응답 절로 이동⑤ 응답⑥ ArgoCD sync 절로 이동⑥ ArgoCD sync⑦ 지켜보기 절로 이동⑦ 지켜보기⑧ 결과 기록 절로 이동⑧ 결과 기록⑨ 카드 읽기 절로 이동⑨ 카드 읽기

2.1 승인 전 PR 생성

  • 승인 전에 PR 을 먼저 연다. 렌더 비교를 통과한 PR 만 카드가 된다
  • 그래서 카드에 PR 번호와 링크, 실제 diff 를 싣고, 사용자는 GitHub 에서도 같은 내용을 본다
  • 승인과 머지가 같은 일이다. 승인을 누르기 전에는 main 이 바뀌지 않는다

2.2 카드에 싣는 내용

칸내용
클러스터에서 실제로 바뀔 것렌더 diff. 지금 main 과 머지 뒤의 비교
레포에서 바뀌는 파일git diff. 접어서 보여 준다
대상 앱경로, 형식, 자동 sync 와 self-heal 여부
예상 효과, 부작용AI 가 변경 툴을 부를 때 적은 값
승인 전에 볼 것별도 검토 단계가 Plan 과 diff 만 보고 쓴 내용
진행 막대승인 → PR 머지 → ArgoCD sync → 앱 상태

2.3 거절과 만료 시 PR 닫기

경우동작확인
거절에이전트가 PR 을 닫고 이유를 코멘트로 남긴다시험 PR #5
결정 없이 카드 만료서버가 Plan 만 닫는다. PR 은 남는다단위 테스트
만료된 PR 이 파일을 쥠같은 파일의 다음 제안이 이 PR 을 닫고 진행한다단위 테스트

세 번째 줄은 리뷰에서 나왔다. 카드가 결정 없이 끝나면 에이전트는 통보를 받지 못한다. 2편의 “같은 파일을 고치는 Kukie PR 이 열려 있으면 기다린다” 규칙 때문에 아무도 닫지 않으면 PR 경로가 영영 막힌다.

3. Recheck — 승인 순간 재확인

3.1 경로 재판단

  • 승인 순간 02편 3장의 경로 판단을 다시 돌린다
  • 판단 결과가 kubectl 이거나, 대상 앱과 레포의 지문이 카드를 만들 때와 다르면 실행하지 않는다
  • 02편의 판단은 AI 를 막는 일이고, 여기는 사용자가 승인한 것과 같은지 대조하는 일이다

3.2 파일 지문과 base 대조

① 승인 요청 절로 이동① 승인 요청② 재확인 절로 이동② 재확인③ 머지 절로 이동③ 머지④ 기록 절로 이동④ 기록⑤ 응답 절로 이동⑤ 응답⑥ ArgoCD sync 절로 이동⑥ ArgoCD sync⑦ 지켜보기 절로 이동⑦ 지켜보기⑧ 결과 기록 절로 이동⑧ 결과 기록⑨ 카드 읽기 절로 이동⑨ 카드 읽기gitops/pull_requests.py — recheck (요약)pull = await gh.pull(int(pull_request["number"]))1if pull.merged:same = (await gh.pull_files(pull.number) == approvedand first_parent == gitops["base_sha"])return Recheck(pull, merged=True) if same else Recheck(pull, "...")if pull.state != "open":2return Recheck(pull, "PR 이 닫혔어요.")if pull.base_ref != gitops["base"] or pull.head_ref != gitops["branch"]:3return Recheck(pull, "PR 의 브랜치가 바뀌었어요.")head = await gh.branch_head(gitops["base"])4if head != gitops["base_sha"]:return Recheck(pull, "... 다른 커밋이 들어왔어요")if await gh.pull_files(pull.number) != approved:5return Recheck(pull, "... 내용이 승인 카드와 달라졌어요")return Recheck(pull)1이미 머지됨같은 내용·같은 base 면 반영으로다르면 직접 확인 안내2PR 닫힘GitHub 에서 누가 닫은 경우3브랜치 변경base 나 head 를 바꾼 경우4main 이 앞섬PR 파일이 그대로여도렌더 결과는 달라질 수 있다5파일 지문 변경파일 경로와 blob id 대조커밋이 아니라 내용을 본다머지 호출에도 head_sha 를 함께 보낸다 — 재확인과 머지 사이에 커밋이 들어오면 GitHub 가 409 로 거부한다조회 자체가 실패하면 STALE 이 아니다. 아무것도 안 했으므로 같은 승인으로 다시 시도한다
승인한 것과 지금이 같아야 머지한다 — 다섯 곳 중 하나라도 다르면 실행하지 않는다

Git 은 파일 내용으로 blob id 를 만든다. 내용이 한 글자만 달라도 값이 달라지므로, 카드를 만들 때 저장한 값과 비교하면 파일이 그대로인지 알 수 있다.

3.3 일시 실패와 재시도

상황결과이유
조회 실패, 시간 초과재시도 (RESUME_RETRYABLE)바뀐 증거가 아니다. 아무것도 안 했다
전제가 바뀜STALE, PR 닫음다시 판단해 새로 제안해야 한다
GitHub 가 머지를 거부 (405, 409)실패, PR 유지사용자가 GitHub 에서 처리한다
머지 요청이 닿았는지 모름올려서 재시도가 판단결과 기록이 없으면 UNKNOWN 으로 닫는다

처음에는 승인 순간 조회가 실패해도 STALE 로 닫았다. 일시적 실패로 승인한 변경과 PR 을 버리는 문제가 리뷰에서 나와 나눴다.

3.4 GitHub 에서 먼저 머지된 PR

  • 승인한 내용 그대로 머지됐으면 변경은 이미 들어갔다. STALE(“실행 안 함”)은 사실과 다르다
  • 그래서 파일 지문과 머지 커밋의 첫 부모가 base_sha 와 같으면 반영으로 보고, sync 지켜보기로 넘어간다
  • 첫 부모만 본다. squash 와 rebase 는 부모가 하나이고, 머지 커밋은 [base, PR head] 두 개이기 때문이다
  • 다른 내용이나 다른 base 위에 머지됐으면 직접 확인하라고 안내한다

3.5 서버 경로 누락

시험 레포의 PR 에 커밋을 더하고 승인하는 시험을 실제로 돌렸다. 머지는 막혔는데 카드가 “결과를 확인하는 중이에요"에서 끝나지 않았다.

① 승인 요청 절로 이동① 승인 요청② 재확인 절로 이동② 재확인③ 머지 절로 이동③ 머지④ 기록 절로 이동④ 기록⑤ 응답 절로 이동⑤ 응답⑥ ArgoCD sync 절로 이동⑥ ArgoCD sync⑦ 지켜보기 절로 이동⑦ 지켜보기⑧ 결과 기록 절로 이동⑧ 결과 기록⑨ 카드 읽기 절로 이동⑨ 카드 읽기에이전트서버 경로Plan카드단위 시험PR #6PR #8mark_stale()POST /plans/{id}/stale실패하면 로그만가짜 서버_stale 있음시험 통과실제 서버와 카드는 이 시험에 없다mark_stale()run 이 닫힐 때 EXPIRED 로닫힐 것으로 가정kukie-server경로 없음404닫히지 않음WAITING_APPROVAL5초마다 읽음"결과를 확인하는중이에요" 계속PR 닫힘, 머지 없음. AI 가 곧바로 새 PR 과 새 카드를 내 run 이 승인 대기로 계속 열렸고, 그래서 옛 Plan 이 닫히지 않았다mark_stale()같은 호출kukie-serverPOST /internal/plans/{planId}/stale 추가닫힘STALE읽기 중단"실행하지 않았어요"
시험의 가짜 서버에는 경로가 있고 실제 서버에는 없었다 — 머지는 막혔지만 카드가 끝나지 않았다
승인 순간 전제가 바뀌어 실행하지 않은 카드
시험 PR #8 의 실제 화면
  • 카드는 Plan 상태를 5초마다 읽는다. 끝난 상태가 오지 않으면 약 11분까지 돈다
  • 에이전트의 단위 테스트는 가짜 서버를 쓴다. 가짜 서버에는 후속으로 미룬 이 경로가 이미 있었다
  • 머지 안전에는 문제가 없었다. 사용자가 멈춘 것으로 오해할 수 있는 표시 문제다

4. Merge Guardrails — 머지의 제약

4.1 브랜치 보호와 PR 유지

① 승인 요청 절로 이동① 승인 요청② 재확인 절로 이동② 재확인③ 머지 절로 이동③ 머지④ 기록 절로 이동④ 기록⑤ 응답 절로 이동⑤ 응답⑥ ArgoCD sync 절로 이동⑥ ArgoCD sync⑦ 지켜보기 절로 이동⑦ 지켜보기⑧ 결과 기록 절로 이동⑧ 결과 기록⑨ 카드 읽기 절로 이동⑨ 카드 읽기guardrail/gitops_hook.py (요약)if pull.mergeable_state == "blocked":1return await _record(plan, GitOpsResult(..., success=False,stderr=f"브랜치 보호 규칙 ... PR 은 남겨 뒀어요: {pull.url}"))merge_commit = await pull_requests.merge(gitops, pull, token, title=...)2async def _after_merge(ctx, plan, gitops, pull, ...):sync = watch.syncing() if auto_sync else watch.manual()3result = await _record(plan, GitOpsResult(...,4merge_commit=merge_commit, sync_result=sync, ...),attempts=RECORD_ATTEMPTS)if auto_sync and plan.execution_result is not None:5gitops_after_merge.start_watch(plan, application, merge_commit, ...)return result1브랜치 보호우회하지 않고 PR 을 남긴다2머지head_sha 고정. 405·409 도 PR 유지3자동 sync 여부꺼져 있으면 수동 안내로 끝4기록 먼저재시도는 기록을 보고 머지 생략5지켜보기기록을 못 했으면 얹을 자리가 없다
머지한 사실을 응답과 지켜보기보다 먼저 저장한다 — 어디서 죽어도 다시 머지하지 않는다
브랜치 보호로 PR 에서 멈춘 승인 카드
브랜치 보호에 걸린 카드 — 우회하지 않고 PR 을 남긴다. 픽스처 화면이며 이름은 예시로 바꿨다

4.2 동시 변경 잠금

대상방식막는 일
같은 방방 잠금. 처리 중 승인과 재개는 409 BUSY같은 카드를 두 번 눌러 머지 두 번
같은 파일레포·파일 단위 키서로 덮어쓰는 PR 두 개
같은 PR머지 호출의 head_sha재확인과 머지 사이의 새 커밋

4.3 머지 기록 우선 저장

  • 기록을 끝내 못 하면 지켜보기를 시작하지 않는다. 얹을 자리가 없기 때문이다
  • 이 경우 결과의 stderr 에 경고를 붙이고, 재실행은 차단한다
  • 지켜보는 동안 앱은 Plan 의 sync_result 를 읽어 진행을 표시한다
머지 뒤 정상으로 끝난 승인 카드
머지 뒤의 승인 카드 — sync 결과가 Plan 에 쌓이면 카드가 바뀐다. 픽스처 화면이며 이름은 예시로 바꿨다

5. Incident — 머지 승인 HTTP 504

5.1 첫 실제 시험의 60초 끊김

시험 레포의 첫 PR 을 승인했더니 화면에 504 가 떴다. 머지는 성공했고, 클러스터 반영도 정상이었다.

시각 (UTC)일어난 일
12:22:23PR #1 머지 성공
머지 뒤웹 로그에 승인 요청 499, 60.0초
12:26ArgoCD 반영 정상

변경은 성공했는데 사용자에게는 실패로 보였다. 다시 누르면 머지를 또 시도할 위험도 있었다. 그때까지 테스트와 픽스처로만 확인했고, 픽스처에는 CloudFront 의 시간 제한과 ArgoCD 의 조회 주기가 없었다.

5.2 sync 대기와 CloudFront 제한

① 승인 요청 절로 이동① 승인 요청② 재확인 절로 이동② 재확인③ 머지 절로 이동③ 머지④ 기록 절로 이동④ 기록⑤ 응답 절로 이동⑤ 응답⑥ ArgoCD sync 절로 이동⑥ ArgoCD sync⑦ 지켜보기 절로 이동⑦ 지켜보기⑧ 결과 기록 절로 이동⑧ 결과 기록⑨ 카드 읽기 절로 이동⑨ 카드 읽기0초60초120초180초240초CloudFront 60초고치기 전PR #1화면 504, web 로그 499요청이 sync 끝까지 열려 있음 (최대 180초 대기)머지에서 반영까지 약 3분 37초고친 뒤PR #2승인 응답 200, 15.6초머지에서 sync 완료까지 약 71초고친 뒤PR #3승인 응답 200, 12.8초머지에서 sync 완료까지 약 2분 19초승인 요청끊긴 요청반영 (고치기 전)반영 (요청 밖에서 확인)ArgoCD 는 웹훅이 없으면 git 을 기본 3분마다 읽는다 — 정상 동작에서도 60초를 넘길 수밖에 없는 구조였다
제한을 늘리지 않고 요청을 줄였다 — sync 는 원래 몇 분 걸리는 일이다

제한을 늘리는 방법은 쓰지 않았다. CloudFront 를 늘려도 ALB 의 유휴 제한(60초)이 남고, 그동안 사용자는 도는 버튼만 본다.

5.3 응답과 지켜보기의 분리

항목고치기 전고친 뒤
승인 요청이 하는 일머지 + sync 대기머지까지
sync 대기요청 안, 180초요청 밖, 600초
결과를 받는 곳응답 본문Plan (sync_result)
앱의 읽기 상한60번130번, 약 11분
① 승인 요청 절로 이동① 승인 요청② 재확인 절로 이동② 재확인③ 머지 절로 이동③ 머지④ 기록 절로 이동④ 기록⑤ 응답 절로 이동⑤ 응답⑥ ArgoCD sync 절로 이동⑥ ArgoCD sync⑦ 지켜보기 절로 이동⑦ 지켜보기⑧ 결과 기록 절로 이동⑧ 결과 기록⑨ 카드 읽기 절로 이동⑨ 카드 읽기conversations_api.py — _finish_locked (요약)task = asyncio.create_task(owned())1gitops_after_merge.keep_approval(task)task.add_done_callback(_log_detached)await asyncio.wait({task}, timeout=APPROVE_WAIT)2if task.done():3return task.result()return JSONResponse(status_code=202, content={4"kind": "accepted", "plan_ids": plan_ids,"message": "승인은 받았고 처리는 계속돼요. ...",})1요청과 떼어 돌림연결이 끊겨도 일은 끝까지 간다. 방 잠금도 끝까지 쥔다2APPROVE_WAIT 45초CloudFront 60초보다 짧다3안에 끝나면 200지금까지 실제 시험은 모두 여기다4넘으면 202앱은 Plan 과 대화 기록을 읽어 결과를 받는다
오래 걸려도 일은 멈추지 않는다 — 앞단이 끊기 전에 먼저 답한다
  • 앱은 진행 스트림이 아니라 Plan 을 읽는다. 새로고침해도 같은 상태를 보여 주기 위해서다
  • 끊긴 요청의 핸들러는 취소되지 않는다. uvicorn 소켓을 띄운 시험으로 확인했다
  • 배포 순서는 서버 → 에이전트 → 앱이다. 에이전트가 먼저 나가면 sync-result 가 404 라 Plan 이 syncing 에 남는다

5.4 재시작 복구와 중복 머지 방지

에이전트가 재시작되면 메모리의 승인 티켓과 지켜보기가 사라진다. 기동할 때 서버에 끝나지 않은 PR 경로 계획을 물어 닫는다.

재시작 때 상태처리상태
머지 기록 있음 (syncing)이어 지켜보지 않음. 사용자 토큰이 없어 연결 정보를 받지 못한다interrupted
머지 기록 없음, GitHub 에서 머지됨머지 사실을 기록APPLIED + interrupted
머지 기록 없음, PR 열림PR 을 남기고 다시 하라고 안내FAILED
머지 기록 없음, 알 수 없음사용자가 PR 을 확인UNKNOWN

6. Stale Answer — 카드와 다른 AI 답변

6.1 카드 “정상"과 답변 “아직 4개”

504 를 고친 뒤 시험 PR #3 에서 카드와 답변이 서로 다른 말을 했다.

① 승인 요청 절로 이동① 승인 요청② 재확인 절로 이동② 재확인③ 머지 절로 이동③ 머지④ 기록 절로 이동④ 기록⑤ 응답 절로 이동⑤ 응답⑥ ArgoCD sync 절로 이동⑥ ArgoCD sync⑦ 지켜보기 절로 이동⑦ 지켜보기⑧ 결과 기록 절로 이동⑧ 결과 기록⑨ 카드 읽기 절로 이동⑨ 카드 읽기01:27:56 머지01:28:06 승인 응답01:30:15 sync 완료승인 카드머지하는 중sync 지켜보는 중반영됐어요 — 정상AI 답변ArgoCD 가 아직 동기화하지 않아 4개 상태입니다 (원인·근거 칸 포함)클러스터web 4개web 2개변경은 정상인데 2분 뒤에 읽으면 답이 틀린 말이 된다 — "원인" 칸 때문에 고장 보고처럼 읽힌다
답은 쓴 순간에 고정되고 카드는 계속 바뀐다 — 둘 다 그 시점에는 맞는 말이다

6.2 충돌한 지시

① 승인 요청 절로 이동① 승인 요청② 재확인 절로 이동② 재확인③ 머지 절로 이동③ 머지④ 기록 절로 이동④ 기록⑤ 응답 절로 이동⑤ 응답⑥ ArgoCD sync 절로 이동⑥ ArgoCD sync⑦ 지켜보기 절로 이동⑦ 지켜보기⑧ 결과 기록 절로 이동⑧ 결과 기록⑨ 카드 읽기 절로 이동⑨ 카드 읽기머지 결과의 안내문"아직 반영됐다고 말하지 마라""반영됐다"만 막는다. "아직 안 됐다"는 막지 않는다시스템 프롬프트 5번"실행 후에는 결과를 확인하고 복기한다"kubectl 에 맞춘 규칙이다. PR 은 머지 뒤 시간이 걸린다시스템 프롬프트 4번"원인을 찾으면 finding 에 담는다"기다리는 상태도 문제 분석 양식으로 나온다AI 답변머지 직후에 쓰고그대로 고정된다화면에 남는 글ArgoCD 가 아직동기화하지 않아4개 상태입니다원인근거AI 의 잘못이 아니라 지시의 문제다 — 다만 문구만 고치면 모델이 또 어길 수 있어 코드로 막는다PR #2 · #3 응답 로그에 조회가 있었고 수정 뒤 PR #4 에는 없었다. 조회가 머지 앞뒤 어디였는지는 로그로 가리지 못했다
반영 전 숫자를 말하도록 만드는 지시가 세 곳에 있었다

6.3 코드 문장으로 교체

02편과 같은 판단이다. 안내문과 프롬프트 5번도 고쳤지만, 화면에 나가는 문장은 코드가 정한다. 실행 단계 블록을 코드가 채우는 자리가 이미 있어 같은 곳에 넣었다.

항목값
답변“PR #N 머지가 끝났어요. 반영 결과는 위 카드에서 볼 수 있어요.”
원인·근거 칸, 다음 행동 안내비움
답변 미리보기같은 문장을 보낸다. AI 의 글이 잠깐 보였다 바뀌지 않게 하기 위해서다
다음 질문평소대로 AI 가 조회해 답한다

시험 PR #4 에서 이 문장이 나왔고, 머지 뒤 응답은 약 10초에서 약 5초로 줄었다. 한 번 잰 값이다.

6.4 교체하면 안 되는 경우

첫 구현은 “머지 결과가 syncing 이면 교체"였다. 리뷰 세 판에서 지적 9건이 나왔고, 대부분 교체하면 설명이 사라지는 경우였다.

① 승인 요청 절로 이동① 승인 요청② 재확인 절로 이동② 재확인③ 머지 절로 이동③ 머지④ 기록 절로 이동④ 기록⑤ 응답 절로 이동⑤ 응답⑥ ArgoCD sync 절로 이동⑥ ArgoCD sync⑦ 지켜보기 절로 이동⑦ 지켜보기⑧ 결과 기록 절로 이동⑧ 결과 기록⑨ 카드 읽기 절로 이동⑨ 카드 읽기response.py — merged_replynumbers: list[Any] = []for msg in _current_turn(ctx.messages):parts = [... if part.tool_name in CHANGE_TOOL_NAMES]1merged = [part.content for part in parts if _is_waiting_merge(part)]2if numbers or merged:if len(merged) != len(parts):3return Nonenumbers += [result.pull_request["number"] for result in merged]elif any(isinstance(part, ToolReturnPart) for part in parts):4return Noneif not numbers:return Nonepulls = ", ".join(f"PR #{number}" for number in numbers)5return f"{pulls} 머지가 끝났어요. 반영 결과는 위 카드에서 볼 수 있어요."1변경 툴만 센다답 양식의 형식 재시도는 제외2기다리는 머지성공 · stderr 없음 · syncing · 번호3같이 또는 뒤의 결과실패 · 거절 · kubectl 이면 AI4앞의 결과돌아온 결과가 있으면 AI5정해진 문장원인·근거 칸과 다음 안내는 비움
확실할 때만 코드가 쓴다 — 조금이라도 다르면 None 을 돌려주고 답은 AI 가 쓴다
경우문제처리
기록을 못 한 머지지켜보기가 돌지 않는데 “카드에서 보라"고 안내. 경고도 사라짐AI 가 쓴다
함께 보낸 변경의 실패, 거절실패 설명이 덮임AI 가 쓴다
답 양식의 형식 재시도실패로 착각해 수정이 통째로 꺼짐세지 않는다
GitHub 에서 먼저 머지된 PRKukie 가 머지한 것처럼 읽힘문장을 “머지가 끝났어요"로 바꿈
PR 번호 없음“PR #None”AI 가 쓴다

형식 재시도는 코드로 읽어서는 확신할 수 없어, 실제 흐름을 재현하는 시험을 넣어 확인했다.

7. Discussion

7.1 확인 범위

내용확인 방법
승인 → 머지 → 응답시험 레포 PR #1~#4, #7
sync 결과와 카드 “정상”PR #2, #3
STALE, 머지 차단, 카드 안내PR #6, #8
거절과 PR 닫기PR #5
머지 직후 답변 문장PR #4
202 응답시험만. 실제 응답은 모두 45초 안에 끝났다
재시작 복구, 중복 클릭, 먼저 머지된 PR시험만
main 이 앞선 경우, 브랜치 보호시험과 픽스처만
Kustomize 앱의 머지실제 기록 없음

7.2 채팅 승인과 GitHub 리뷰 생략

  • Kukie 의 승인은 GitHub 리뷰를 대신한다. App 이 리뷰 없이 머지한다
  • 브랜치 보호가 리뷰를 요구하면 머지하지 않고 PR 을 남긴다. 팀의 규칙을 우회하지 않기 위해서다
  • 카드의 diff 를 사람이 읽는다는 전제가 남는다. 작은 변경에서는 편하고, 큰 변경에서는 약한 지점이다

7.3 AI 호출을 없애는 구조

지금 구조는 AI 를 한 번 부른 뒤 그 글을 버린다.

  • 쓰지 않을 답에 비용과 대기 시간이 든다
  • 대화 기록의 출력 툴 인자에 원래 글이 남아, 다음 턴에 모델이 볼 수 있다
  • 머지 앞에서 ToolFailed 로 끝난 다른 변경은 정상 재시도와 구분되지 않아, 그 설명이 덮일 수 있다

머지가 끝나면 모델을 부르지 않고 코드가 바로 답하면 앞의 둘이 사라진다. 수정 범위가 커서 비용과 대기 시간이 문제가 될 때 한다.

핵심 메시지

  • 승인의 대상: 커밋이 아니라 내용과 전제
    • 파일 지문, base, 브랜치, 경로가 모두 같아야 머지한다
  • 긴 작업: 제한을 늘리지 않고 요청과 분리
    • 응답은 머지까지, 결과는 Plan 에 쌓고 화면이 읽는다
  • 기록 순서: 머지한 사실이 먼저
    • 어디서 죽어도 재시도가 다시 머지하지 않는다
  • 고정되는 글: 바뀌는 상태를 적지 않는다
    • 카드가 맡는 상태는 답변에 쓰지 않고, 코드로 막는다
  • 가짜 서버의 한계: 시험을 통과해도 경로가 없을 수 있다
    • 504 와 STALE 은 모두 실제 시험에서 처음 드러났다

8. References

head SHA 를 지정한 머지와 거부 응답. 3장과 4장의 근거다.
→ GitHub REST API — Merge a pull request
→ GitHub REST API — Git blobs
브랜치 보호와 머지 가능 상태. 4.1 의 근거다.
→ GitHub Docs — About protected branches
ArgoCD 의 git 조회 주기와 sync 상태. 5.2 의 근거다.
→ Argo CD — Git Webhook Configuration
→ Argo CD — Automated Sync Policy
CloudFront 의 원본 응답 제한과 202 응답의 의미. 5장의 근거다.
→ Amazon CloudFront — Response timeout
→ MDN — 202 Accepted