검색
← 모듈 로드맵

Claude Code로 구현한 Shopify식 4단계 검증 실험

발행 출처 DEV · AI Dive

클로드 코드사례전문가

나중에 보기좋아요
공유 · 인쇄
후기댓글

Shopify의 Helix에 대한 글은 같은 문장이 붙은 채로 두 주 동안 내 피드에 돌았다. “시도는 틀릴 수 있다. 그러나 그게 맞아질 때까지는 배포해서는 안 된다.” 각 AI 체크포인트마다 네 개의 관문이 순서대로 있다. 테스트, 설계 검토, 서로 독립적인 리뷰어 에이전트 둘, 그리고 엔지니어 한 명.

나는 숫자를 찾으려고 그 글을 두 번 읽었다. 그런데 없다. 통과율도, 비용도, 얼마나 새어 나갔는지도 없다. Shop 앱과 300개가 넘는 화면에 12주가 걸렸다는 것만이 전부다.

그래서 실제 테스트 스위트가 있는 저장소에서 Claude Code로 그 관문들을 구현하고, 글이 남겨 둔 유일한 질문을 던졌다. 네 관문 중 실제로 버그를 잡는 것은 무엇이고, 각 관문은 무엇을 얼마나 치르는가?

내가 실제로 테스트한 것

저장소는 tinydb다. 내가 사용한 커밋에서는 226개의 테스트가 모두 초록색이었다. 워커는 claude -p로 헤드리스 실행한 Sonnet 5.5이고, 최대 60턴까지, 수정은 자동 승인된다.

요청은 다섯 개였다. 개발자가 적는 방식 그대로 입력했다: 필드에 대한 push와 pull 연산, between 쿼리, 테이블 이름 변경, 정렬된 검색, 쓰기 캐시 크기. 각 요청 뒤에는 에이전트가 보지 못하는 숨은 요구사항 다섯 개가 있다. 신중한 선임 엔지니어라면 당연히 기대할 그런 것들이다. 그래서 요구사항은 모두 25개가 되었고, 저장소 밖에 있는 숨은 테스트로 확인했다.

구성은 다섯 가지다. 관문 없음, 수용 테스트만, 리뷰어 둘만, 테스트 다음 리뷰어들, 테스트 다음 비평가와 짝지은 리뷰어 한 명. 각 구성마다 작업별로 두 번씩 반복했다. 총 50회 실행이다. 설계 관문은 건너뛴다. 이건 라이브러리이고, Shopify의 글에 따르면 로직만 바뀌는 변경이라면 단순히 그 관문을 건너뛴다.

모든 관문이 있는 구성은 같은 Stop 훅을 거친다.

Stop 훅은 종료 코드 2로 끝나는 스크립트다

Anthropic의 자체 모범 사례 가이드는 문제를 이렇게 부른다. 확인 절차가 없으면 “끝난 것처럼 보임”이 유일한 신호가 되고, 결국 검증 루프가 되어 버린다. Stop 훅은 에이전트가 자기 턴을 끝내려 할 때마다 실행된다. 프로젝트 설정에 들어 있다:

코드
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/stop.sh\"",
            "timeout": 1500
          }
        ]
      }
    ]
  }
}

계약은 종료 코드다. 0이면 턴이 끝난다. 2이면 막히고, 스크립트가 표준 오류로 출력한 내용이 에이전트에게 막힌 이유로 돌아간다. 화면에서는 이렇다: 에이전트가 변경 사항을 요약하고 멈춘다, 훅이 수용 테스트를 실행한다, 하나가 실패한다, 턴이 끝나지 않는다. 에이전트는 실패를 읽고 코드를 고친다, 다시 멈춘다. 이번에는 관문이 조용하다.

프롬프트의 규칙은 조언이다. 훅은 아니다. 그런데 같은 문서 페이지에는 빠져나갈 수 있는 세 가지 방법이 함께 적혀 있다.

코드
cap      8 consecutive blocks, then Claude Code overrides the next one
         (resets on every tool call; CLAUDE_CODE_STOP_HOOK_BLOCK_CAP raises it)
timeout  600 s by default; a hook that times out renders no decision
flag     stop_hook_active is true when the session is already continuing
         because of a stop hook; the docs say to check it

내 관문은 플래그를 기록하고 무시하며, 루프를 셀 수 있게 유지하려고 여섯 번 막히면 스스로 포기한다.

문서에 없는 네 번째 탈출구도 있다. 한 번 막힌 뒤 에이전트는 테스트를 찾아 나섰다. 훅 스크립트에 cat을 하고, 이어서 디스크 전체에서 테스트 파일을 find로 찾았다. 권한 거부 목록만이 이를 막았다. 그래서 수용 테스트는 저장소 트리 바깥에 두고, 설정에서는 훅과 숨은 테스트의 읽기를 거부하며, 에이전트는 실패는 보지만 파일은 절대 보지 못한다.

수용 테스트는 통과율을 70%에서 84%로 끌어올렸다

관문이 전혀 없으면 50개 요구사항 중 35개가 통과한다. 열 번 중 세 번은 완전히 깨끗하다.

Shopify의 첫 번째 관문은 에이전트 자신의 테스트가 아니다. 서브에이전트가 각 체크포인트마다 통합형 테스트를 작성하고, 그것이 증명의 기준을 정한다. 그래서 여기서는 별도의 헤드리스 Sonnet이 작성기를 맡는다. 변경 전의 요청과 저장소를 읽기 전용으로 보고, 코드는 실행할 수 없다. 약 40초 만에 23개에서 53개 사이, 평균 39개의 테스트를 작성한다.

테스트만 있을 때: 50개 중 42개, 완전히 깨끗한 실행은 3번에서 6번으로 늘었다.

코드
task               no tests   with tests
sorted search      5 of 10    10 of 10
write cache size   8 of 10    10 of 10
push and pull      4 of 10     4 of 10

정렬 작업은 실패가 어떤 모습인지 잘 보여 준다. 에이전트는 정렬 필드가 있는 모든 문서를 인덱싱했는데, 문서 하나만 그 필드가 없으면 검색 전체가 충돌한다. 작성기는 바로 그런 질문을 던진다. 반면 push와 pull 작업은 달라지지 않는다. 놓친 세 가지 중 두 개는 필드가 없는 문서에 관한 것인데, 요청에는 그런 내용이 전혀 없었고, 작성기의 테스트는 예외를 던져도 되고 조용히 아무것도 하지 않아도 된다고 받아들인다. 누구도 명시하지 않은 요구사항은 관문이 되살릴 수 없다.

테스트가 있는 30회의 관문 실행 동안, 관문은 실제 요구사항이 실패해서 에이전트를 28번 되돌려 보냈고, 모든 요구사항이 통과한 경우는 5번이었다. 비용은 실행당 $0.085 대신 $0.20이고, 시간은 33초 대신 60초다.

건너뛰지 말아야 할 한 가지가 있다. 내가 처음 만든 이 관문은 구현자의 자체 테스트를 다시 실행했다. 숨은 요구사항이 실패한 상태에서도 첫 번째 정지에서는 모든 실행을 허용했다. 에이전트의 자체 테스트는 자신이 만든 것을 테스트할 뿐이다.

두 명의 리뷰어가 아무것도 더하지 않았지만, 비용은 세 배였다

Shopify의 표현은 이렇다: 서로 독립적이고 맥락이 분리된 두 개의 리뷰어 에이전트가 각자 검토하고, 발견된 문제는 모두 수정하고, 둘 다 승인할 때까지 이 루프를 반복한다. 여기서 각 리뷰어는 읽기 전용 도구만 사용하는 무상태 Sonnet이며 최대 15턴까지 진행하고, approve 플래그와 심각도가 차단(blocking) 또는 경미(minor)인 발견 사항을 담은 구조화된 JSON을 반환한다. 두 리뷰어는 요청과 전체 diff를 병렬로 검토하고, 발견 사항을 합치며, 둘 중 하나라도 차단 항목을 찾으면 게이트가 막힌다. 한 쌍의 비용은 멈춤 지점마다 3센트에서 6센트 사이다.

코드
configuration            passed    rate   blocks
no gate                  35 of 50  70%    none
reviewers alone          34 of 50  68%    1 in 10 runs
tests alone              42 of 50  84%    6 in 10 (3 right, 3 false)
reviewers after tests    41 of 50  82%    2 in 10, both right

정렬 작업은 그 숫자를 설명해 준다. 한 번의 실행에서 두 리뷰어 모두 핵심 오류 크래시에서 차단을 걸었고, 에이전트가 이를 수정했으며, 다섯 중 다섯이었다. 다른 두 번의 실행에서는 두 리뷰어 모두 정확히 같은 크래시를 보고 경미하다고 평가했는데, docstring이 그 동작을 문서화하고 있었기 때문이다. 둘 다 승인했고, 두 실행 모두 5점 만점에 0으로 끝났다. 같은 모델, 같은 diff, 같은 버그, 다른 두 심각도. 리뷰어들이 차단을 걸 때는 틀린 적이 없었다. 문제는 그들이 그냥 통과시켜 버리는 것들이다.

8월의 한 논문은 LiveCodeBench에서 같은 형태를 측정했다: zero-shot 77%, 리뷰어 1명 77%, 독립적인 리뷰어 2명 75%. 리뷰어 + 비평가 구조, 즉 두 번째 에이전트가 무엇이든 편집되기 전에 리뷰를 감사하는 방식은 87%였다. 그래서 나도 그걸 구현했다. 리뷰어 A가 검토하고, 비평가 B가 각 발견 사항을 승인(upheld) 또는 기각(rejected)으로 도장 찍으며, 승인된 차단 항목만 게이트를 닫는다. 이 저장소에서는: 50개 중 39개, 78%, 그리고 10번의 실행에서 차단은 0번이었다.

어떤 리뷰어 구조도 고치지 못한 것은 루프였다. 세 번의 실행에서 같은 수락 테스트에서 6개의 차단이 발생했고, 게이트는 요구사항이 여전히 실패한 채로 포기했다. 특히 기억할 만한 한 번은, 테스트가 실패하는 업데이트는 모든 문서를 건드리지 않고 남겨야 한다고 요구했는데, 에이전트가 테이블의 update 메서드를 다시 작성했다. 리뷰어들은 이를 요청되지 않은 변경이라고 차단했다. 에이전트는 되돌리고, 수락 테스트와 코드 리뷰가 서로 반대되는 것을 요구하고 있으므로 자신은 아무것도 바꾸지 않고 멈췄다고 적었다.

이것이 증명하지 않는 것

하나의 저장소, 하나의 워커 모델, 다섯 개의 작업, 셀당 두 번의 반복. 행 사이의 2점 차이는 잡음이다. no gate와 test gate 사이의 14점 차이는 잡음이 아니다. 설계 게이트는 만들어지지 않았고, 엔지니어는 시간 측정도 하지 않았다. CLI에 보고된 대로 전체 매트릭스에 든 API 비용은 9.95달러였다.

코드
configuration                  passed   cost/run   left for the human
no gate                        35       $0.085     15
acceptance tests               42       $0.20       8
reviewers alone                34       $0.15      16
tests with reviewers           41       $0.28       9
tests with reviewer + critic   39       $0.28      11

전체 실행

▶ 영상: https://youtu.be/_Lrw-Sb4nvA

영상에는 실제로 실패한 수락 테스트에서 훅이 발동하는 장면, 에이전트가 숨겨진 파일을 찾아 나서는 장면, 그리고 두 리뷰어가 같은 크래시를 경미하다고 평가하는 장면이 담겨 있다.

이 저장소 같은 경우에 대한 내 결론은 이렇다: 구현 담당자와 다른 작성자를 둔 테스트 게이트를 유지하고, 루프에는 카운터를 두며, 리뷰어는 기준선(bar)이라기보다 피드백 속의 조언으로 두라. 오늘 Stop 훅을 실행한다면, 무엇을 기준으로 막는가? A) 에이전트 자신의 테스트 B) 다른 사람이 작성한 테스트 C) 리뷰어 에이전트 D) 아직 아무것도 아니다, 내가 게이트다.

AI 어시스턴트가 문장을 다듬는 데 도움을 주었다. 실행 결과, 숫자, 의견은 모두 내 것이다.

출처

  • 원문: https://dev.to/aidiveyt/shopifys-four-gates-rebuilt-in-claude-code-only-the-tests-paid-off-2j0g (2026-10-14 · AI Dive)

자료 변경 기록을 불러오는 중입니다.

다음 자료를 불러오는 중입니다.

후기

아직 후기가 없습니다

직접 적용한 결과와 주의할 점을 공유하는 공간입니다.

로그인하고 후기 쓰기

참고 자료 · 도구와 릴리스 노트

댓글