2026년 7월 16일 | 바이브코딩 Tips
포트 4317에서 API 테스트가 초록색으로 끝났는데, 방금 고친 코드는 한 번도 실행되지 않았다. 코딩 에이전트가 새 서버를 띄우려 했지만 포트는 이전 프로세스가 이미 잡고 있었고, 시작 명령은 곧바로 종료됐다. 그 뒤의 검증 요청은 멀쩡히 응답한 이전 서버로 향했다. 나는 이 장면을 겪고 나서 응답이 맞다는 사실과 수정한 서버가 응답했다는 사실을 따로 보기 시작했다.
그림에서 수정 코드와 검증 응답 사이의 선은 직선이 아니다. 새 프로세스가 실제로 뜨지 못한 순간, 요청은 계속 살아 있던 이전 프로세스로 갈라진다. HTTP 상태와 본문만 보면 결과는 맞지만, 새 PID·시작 로그·코드 revision을 함께 보지 않으면 어떤 실행물이 답했는지는 남지 않는다.
새 서버가 뜨지 않았는데 테스트는 초록색
누락된 리소스가 500을 내던 API를 404로 고치는 작업이었다. 에이전트는 예외 매핑을 수정하고 서버 시작 명령을 백그라운드로 보낸 뒤, 같은 포트에 요청을 날려 404를 확인했다. 보고만 읽으면 수정과 검증이 자연스럽게 이어진다. 하지만 시작 로그 앞부분에는 짧게 address already in use가 있었고, 새 프로세스는 요청을 받기 전에 끝난 상태였다.
더 헷갈렸던 이유는 이전 서버도 404를 반환했다는 점이다. 다른 worktree에서 비슷한 실험을 하며 띄운 프로세스가 남아 있었다. 새 코드가 틀렸다고 단정할 수도 없고, 맞다고 증명한 것도 아니었다. 검증 대상의 정체만 비어 있었다. PASS는 기대 응답을 확인했지만 패치의 효력은 확인하지 못했다.
이런 false positive는 응답이 단순할수록 잘 숨는다. 상태 코드, health 문구, 정적 JSON처럼 여러 버전에서 우연히 같을 수 있는 값은 실행물 식별력이 낮다. 테스트가 까다로운 계산 결과를 확인했더라도 입력 fixture나 데이터베이스가 공유되어 있으면 다른 branch의 서버가 같은 답을 낼 수 있다. 기능 assertion이 촘촘한 것과 실행 대상이 정확한 것은 별도의 검증 축이다.
포트가 열렸다는 사실은 시작 성공이 아니다
에이전트가 서버를 띄운 직후 health endpoint를 호출하는 흐름은 흔하다. 여기서 포트가 열리고 200이 돌아오면 준비 완료로 보기 쉽다. 하지만 고정 포트에서는 누가 열었는지가 더 중요하다. 이전 터미널의 개발 서버, 다른 worktree, 테스트 러너의 자식 프로세스, 재시작 정책이 붙은 컨테이너가 같은 주소를 계속 지킬 수 있다.
그래서 나는 서버 시작을 명령 실행과 포트 응답의 두 단계로만 두지 않는다. 시작 명령의 PID가 살아 있는지, 그 PID가 해당 포트를 LISTEN하는지, 시작 로그가 준비 완료 지점까지 갔는지를 한 묶음으로 본다. 셋 중 하나가 맞지 않으면 요청 테스트를 진행해도 패치 검증 결과로 채택하지 않는다.
요청 전에 실행물의 표식을 고정한다
가장 단순한 방법은 새 프로세스가 자기 정체를 보여 주게 하는 것이다. 개발 환경의 health 응답이나 시작 로그에 짧은 revision, 시작 시각, instance ID 가운데 하나를 넣는다. 민감한 경로나 전체 환경변수는 필요 없다. 지금 요청을 받은 실행물이 방금 빌드한 코드와 같은지만 구분할 수 있으면 된다.
{
"ready": true,
"revision": "a13c9e2",
"started_at": "2026-07-16T11:24:08+09:00"
}
검증 순서도 응답 테스트보다 표식 확인을 앞에 둔다. 시작 명령에서 얻은 PID를 기록하고, 준비 완료 로그를 기다리고, health 응답의 revision이 현재 소스와 맞는지 본 다음 기능 요청을 보낸다. 이때 포트가 먼저 열렸더라도 표식이 다르면 이전 서버로 판정한다. 포트 번호보다 실행물의 provenance를 먼저 맞추는 셈이다.
revision만 같아도 충분하지 않은 경우가 있다. 같은 commit에서 환경변수나 feature flag가 다르면 동작이 갈릴 수 있어서다. 나는 재현에 영향을 주는 값 가운데 안전한 항목만 profile 이름이나 설정 digest로 줄여 함께 남긴다. 전체 설정을 노출하지 않으면서도 로컬, 테스트, staging 중 어느 실행 경로였는지 구분할 수 있다.
임시 포트도 소유권과 함께 넘긴다
테스트마다 빈 포트를 할당하면 충돌 가능성은 줄어든다. 다만 “빈 포트 하나 찾아서 서버와 테스트가 알아서 쓰게 해”라고만 하면, 포트를 고른 프로세스와 요청을 보내는 프로세스 사이에서 값이 어긋날 수 있다. 나는 하네스가 포트를 하나 정하고 서버 시작 결과와 테스트 입력에 같은 값을 명시적으로 넘기게 한다. 자동으로 다음 번호를 찾는 서버 기능에는 기대지 않는다.
시작 실패도 조용히 넘기지 않는다. 서버 프로세스가 준비 완료 전에 종료하면 기능 테스트를 실행하지 않고, 종료 코드와 시작 로그를 실패 원인으로 올린다. 반대로 준비가 끝났다면 기능 테스트 보고에 PID, 포트, revision을 짧게 붙인다. 결과 한 줄이 길어지지만, “어떤 서버가 답했나”를 다시 추적하는 시간보다 훨씬 싸다.
여러 에이전트가 병렬로 움직이는 환경에서는 포트 배정표도 짧게 필요하다. 세션별 임시 디렉터리에 포트와 PID를 기록하고, 이미 예약된 포트를 다른 worker가 재사용하지 못하게 한다. 작업 이름만으로 구분하면 재시작 뒤 소유권이 흐려지므로 생성 시각과 실행 디렉터리까지 같이 둔다. 이 정보는 정리 단계에서 남의 프로세스를 건드리지 않는 경계가 된다.
종료 확인까지 같은 작업에 묶는다
이 문제는 시작 검증만 고쳐도 다시 생길 수 있다. 작업이 끝난 뒤 에이전트가 자신이 띄운 서버를 정리하지 않으면 다음 세션의 이전 프로세스가 된다. 그래서 서버를 시작한 주체가 PID를 보관하고, 성공·실패와 무관하게 그 PID만 정상 종료한 뒤 포트가 닫혔는지 확인하게 한다.
여기서 포트의 모든 프로세스를 강제로 죽이는 방식은 피한다. 다른 작업의 서버를 끊을 수 있기 때문이다. 시작한 PID와 자식 프로세스 범위를 알고 있을 때만 정리하고, 소유권이 불분명하면 종료 대신 정체를 보고한다. 시작과 검증과 정리를 하나의 수명 주기로 묶어야 다음 실행의 증거도 깨끗해진다.
정리 중 실패도 숨기지 않는다. 부모 PID는 끝났지만 watcher나 자식 worker가 포트를 계속 잡는 경우가 있다. 이때 “서버 종료 완료” 대신 남은 LISTEN 주체를 보여 주고 다음 작업을 보류한다. 시작 전 preflight와 종료 후 postflight가 같은 포트를 확인해야 한 세션의 실행물이 다음 세션으로 새지 않는다.
내가 완료 전에 확인하는 네 가지
서버를 띄워 검증한 작업에서는 네 가지만 본다. 새 시작 명령이 준비 완료까지 갔는가, 기록한 PID가 실제 LISTEN 주체인가, health 표식이 현재 revision과 맞는가, 작업 뒤 그 PID와 포트가 정리됐는가. 하나라도 비면 HTTP 결과는 참고값으로만 남기고 패치 완료 근거에서는 뺀다.
코딩 에이전트는 요청과 응답을 빠르게 연결하지만, 그 사이의 프로세스 정체는 쉽게 생략한다. 나는 이제 “404가 나왔다”보다 “방금 띄운 revision a13c9e2가 404를 반환했다”를 완료 문장으로 받는다. 같은 포트에서 같은 응답이 돌아와도 실행물이 다르면 전혀 다른 검증이다.
'[개발 깨알 상식_Tips] > [바이브코딩 Tips]' 카테고리의 다른 글
| 코딩 에이전트 리팩터링에서 뒤바뀐 source와 destination (1) | 2026.07.30 |
|---|---|
| 잘린 검색 결과를 전체 목록으로 믿은 코딩 에이전트 (0) | 2026.07.15 |
| mock에서는 통과하고 실제 API에서 깨진 수정 (0) | 2026.07.14 |
| 기본값 패치를 무력화한 호출부의 명시적 인자 (0) | 2026.07.13 |
| 코딩 에이전트가 넓힌 .gitignore 규칙 (0) | 2026.07.12 |