15장 실패에서 출발하는 트러블슈팅
서버가 열리지 않는다
EADDRINUSE이면 포트 충돌이다. PORT=5173 npm start로 확인한다. node --version이 20 미만이면 현재 LTS로 올린다. 시작 직후 종료되면 첫 오류의 파일과 줄을 본다. 연쇄 오류의 마지막 줄부터 쫓지 않는다.
화면은 열리지만 목록이 비었다
Network 탭에서 /api/briefs 상태 코드를 본다. 404면 URL, 500이면 서버 터미널, 200인데 비면 JSON 키와 loadList 계약을 비교한다. Console의 첫 예외를 고친다.
제출이 422다
응답의 errors 배열을 확인한다. UI 값이 아니라 실제 Request Payload를 본다. 체크박스는 선택이 하나일 때도 배열이어야 한다. 날짜는 YYYY-MM-DD, 예산은 0 이상의 정수다.
승인은 됐는데 실행이 거절된다
거절 이유를 나눈다. RECEIPT_MISMATCH는 다른 브리프, RECEIPT_EXPIRED는 만료, ARTIFACT_CHANGED는 승인 뒤 산출물 변경이다. 상태를 승인으로 바꾼 뒤 해시를 만들었는지 확인한다. 변경된 제안을 억지로 실행하지 말고 다시 승인한다.
AI 호출만 실패한다
키와 모델 환경 변수가 서버 프로세스에 전달됐는지 확인한다. 401은 인증, 403은 권한, 429는 할당량·속도, 400은 요청 계약을 먼저 의심한다. API 키를 오류 화면이나 이슈에 붙이지 않는다. mock으로 전환해 나머지 기능이 독립적으로 정상인지 확인한다.
한글 캡처가 빈 화면이다
캡처 전용 쿼리로 목표 섹션을 문서 상단에 배치하고 충분한 가상 시간 예산을 준다. 브라우저 프로세스가 남으면 별도 사용자 데이터 디렉터리와 실행 제한 시간을 쓴다. 생성 파일의 크기만 보지 말고 실제 이미지를 열어 확인한다.
부록 A 출시 전 체크리스트
- 원문 보존과 AI 결과 비교가 가능한가
- 돈·기한·권한의 최종 결정자가 명확한가
- 모든 외부 행동에 dry-run과 사람 승인이 있는가
- 승인 뒤 변경을 감지하고 다시 승인하는가
- API 키와 개인정보가 브라우저·로그·fixture에 없는가
- 키보드, 확대, 오류 안내를 실제로 확인했는가
- 새 환경에서
npm run qa가 성공하는가 - 장애 시 mock·수동 처리·롤백 경로가 있는가
부록 B 정답보다 좋은 질문
AI에게 “이 코드 맞아?”라고 묻는 대신 다음을 묻는다. “이 변경이 깨뜨릴 수 있는 불변식 세 가지를 찾고 테스트로 보여 줘.” “사용자 입력이 모델 지시로 오해될 경로를 추적해 줘.” “승인 후 데이터가 바뀌는 경쟁 조건을 재현해 줘.” 좋은 협업은 생성량이 아니라 발견한 위험과 줄인 검토 비용으로 측정한다.
부록 C 90분 완주 실습: 달빛 독립영화제
이 실습은 새 기능을 많이 붙이는 대신 한 흐름을 증거와 함께 완주한다. 시작 전에 npm test 결과를 저장하고 브라우저의 Network와 Console을 연다.
첫 15분에는 새 의뢰 화면에 기본 입력된 달빛 독립영화제 예시를 읽고 폼으로 제출한다. 이 항목은 초기 fixtures/briefs.json 목록이 아니라 독자가 새로 만드는 실습 데이터다. 고객이 원하는 것은 신청 접수와 심사 상태 공유, 승인 뒤 결과 안내다. 성공 결과를 세 개, 자동화가 멈춰야 할 조건을 두 개 적는다. “AI가 알아서 선정”은 요구가 아니라 위험이다. 선정 판단은 범위 밖으로 둔다.
다음 20분에는 폼으로 같은 의뢰를 새로 제출한다. 반환된 브리프 ID, 상태 코드, 위험 배열을 기록한다. 요구 문장에 “미성년 배우 사진 공개”를 추가해 SENSITIVE_FLOW가 생기는지 확인한다. 생성되지 않으면 정규식 한 줄부터 늘리지 말고, 이 신호가 모든 문맥에서 실제 위험인지 테스트 사례를 먼저 논의한다.
다음 20분에는 제안을 승인하고 dry-run을 누른다. 영수증 ID와 해시 앞 16자, 계획된 행동을 기록한다. 실제 실행을 켜지 않는다. 개발자 도구에서 승인 요청과 미리보기 요청의 본문이 어떻게 다른지 비교한다.
다음 20분에는 회귀 테스트를 추가한다. 승인 상태로 바꾼 브리프와 제안으로 영수증을 만든 뒤 executeAction에 dryRun:false를 주면 queued여야 한다. 이어 제안 금액을 바꾸면 denied와 ARTIFACT_CHANGED여야 한다. 테스트 이름은 구현 함수가 아니라 업무 보장을 설명한다.
마지막 15분에는 npm run qa를 실행하고 생성된 세 화면을 직접 연다. 테스트 통과, 실제 화면 캡처, 웹북 생성, 링크 검증이 모두 있어야 완료다. 실패하면 결과를 지우지 말고 첫 실패 단계에서 원인을 찾는다.
기대되는 학습 기록
내가 코드로 고정한 업무 규칙:
AI에게 맡겼지만 사람이 확인할 결과:
승인 없이는 실행되지 않는 외부 행동:
일부러 만든 실패와 이를 잡은 테스트:
출시 전에 추가해야 할 운영 구성요소:
부록 D API 계약 빠른 참조
GET /api/briefs는 목록용 최소 필드를 돌려준다. GET /api/briefs/:id는 원문, 구조화 결과, 규칙 기반 제안, AI 분석, 자동화 계획, 영수증을 제공한다. POST /api/briefs는 JSON 입력을 검증해 201 또는 422를 반환한다. POST /api/briefs/:id/approve는 승인자와 현재 산출물로 영수증을 만든다. POST /api/briefs/:id/automations/run은 기본적으로 dry-run이며, 명시적으로 false여도 유효한 영수증이 없으면 거절한다.
운영 API에는 인증과 권한 검사가 더 필요하다. 목록에는 페이지네이션과 필터, 쓰기 요청에는 CSRF 또는 토큰 정책, 변경에는 낙관적 잠금 버전을 추가한다. 오류 응답은 { error, details, correlationId }처럼 일정하게 만든다. 내부 스택과 비밀값은 응답에 포함하지 않는다.
부록 E 코드 리뷰 대화 예시
요청자는 “승인 버튼을 추가해 줘”라고 끝내지 않는다. “현재 제안 스냅샷을 승인 영수증에 묶고, 24시간 만료와 변경 감지를 넣어 달라. 승인 없는 실제 행동, 승인 후 변경, 정상 큐 등록 테스트를 완료 조건으로 한다”고 쓴다.
AI가 구현하면 리뷰어는 세 질문을 던진다. 첫째, 상태를 바꾸기 전과 후 중 어느 시점의 해시인가. 둘째, JSON 직렬화가 안정적인가. 셋째, 실행 직전까지 무엇이 바뀔 수 있는가. 답은 설명만으로 받지 않고 테스트와 diff에서 확인한다.
AI가 큰 리팩터링을 함께 제안하면 기능과 분리한다. 필요한 최소 패치를 먼저 검증하고 구조 개선은 별도 변경으로 만든다. 작은 변경은 되돌리기 쉽고, 테스트 실패의 원인도 좁다. 반대로 보안 경계가 여러 파일에 흩어진다면 추상화 자체가 위험을 줄이는지 확인한 뒤 함께 이동한다.
참고와 저작권
예제 코드와 도형, 화면, 가상 데이터는 이 책을 위해 새로 제작했다. 조직과 인물은 모두 허구이며 실제 서비스·고객과 관계없다. OpenAI, ChatGPT, Codex 및 그 밖의 제품명은 각 소유자의 상표다. 독자는 각 서비스의 최신 약관과 개인정보 정책을 확인해야 한다.
공식 자료: OpenAI Responses API, Structured Outputs, Tools, Node.js Test Runner, WCAG 2.2, OWASP Top 10