Skip to content

[Preview] serve 프로세스 종료 실패로 낡은 빌드가 계속 서빙됨 (pkill 패턴 미매치) #115

Description

@Danto7632

요약

startPreviewServerpkill -f 'npx serve'실제 serve 프로세스를 한 번도 매치하지 못한다. 그 결과 프리뷰를 다시 띄워도 옛 serve가 포트 3000을 계속 쥐고 있어 낡은 빌드 산출물이 서빙된다. 사용자에게는 "고쳤는데 프리뷰가 그대로"로 보인다.

근거

PreviewWorkspaceService.java:144-151

String buildDir = detectBuildOutputDir(containerId);
dockerService.exec(containerId, "pkill -f 'npx serve' 2>/dev/null || true");
dockerService.exec(containerId, "nohup npx serve -s " + buildDir + " -l 3000 > /tmp/serve.log 2>&1 &");

컨테이너 실측(node:20-alpine, 프로덕션과 동일한 cap-drop/pids/memory 플래그)에서 확인된 사실:

  1. 패턴이 아무것도 안 잡는다. 실행 중 프로세스의 cmdline은 npm exec serve -s /workspace/app/dist -l 3000node /root/.npm/_npx/<hash>/node_modules/.bin/serve -s ...로, 어느 쪽에도 리터럴 npx serve가 없다.
  2. 대신 자기 자신을 죽인다.pkill을 실행 중인 sh -c 셸의 cmdline에는 패턴 문자열 npx serve가 들어 있어 스스로에게 SIGTERM을 보낸다(exec 종료코드 143).
  3. 그래서 옛 serve가 살아남는다. 포트 3000이 점유된 상태에서 serve 14는 EADDRINUSE로 죽지 않고 랜덤 포트로 조용히 옮겨 붙으며 성공 로그를 찍는다(실측: Accepting connections at http://localhost:42547). 게이트웨이는 컨테이너 3000 매핑으로만 프록시하므로(PreviewGatewayServicehostPort) 낡은 serve = 낡은 dist가 계속 응답한다.

대조군 실측: 패턴을 [s]erve -s로 바꾸면 매치 2건 → 정상 종료(0건).

함께 확인된 인접 결함 (같은 "실패가 조용하다" 계열)

  • DockerContainerService.execexec 종료코드를 아예 읽지 않는다(inspectExecCmd 미호출, stdout+stderr 문자열만 반환). 따라서 buildIfConfiguredset -o pipefail도 무의미하고, 빌드 실패가 호출자에게 전달되지 않는다.
  • startPreviewServer는 serve가 실제로 리스닝 중인지 확인하지 않고 성공으로 반환하며, 그 직후 ProjectPreviewProvisioner가 세션을 ACTIVE로 마킹한다.
  • 컨테이너가 재사용되고 prepareProjectgit fetch만 하므로(clean 없음), 이전 성공 빌드의 dist가 남아 있으면 이번 빌드가 실패해도 detectBuildOutputDir이 그 dist를 잡아 낡은 산출물을 성공으로 서빙한다 — PreviewWorkspaceService.java:162-168 javadoc이 명시한 "빌드 실패는 호출자에게 도달해야 한다"는 불변식이 이 경로로 무력화된다.

수정 방향

  1. pkill 패턴을 실제 cmdline에 맞추고 자기 매치를 피한다(예: pkill -f "[s]erve -s").
  2. 기동 후 3000 리스닝을 실제로 확인하고, 아니면 실패로 처리한다(serve 로그의 포트 파싱 또는 netstat/wget 확인).
  3. DockerContainerService.exec에 종료코드 확인 경로를 추가해 빌드/서빙 실패가 조용히 성공으로 넘어가지 않게 한다. 이건 영향 범위가 넓으므로 별도 단위로 분리할 수 있다.

출처

다중 에이전트 조사(2026-08-15, Issue #111 해법 판정 과정)의 컨테이너 실측. 코드 라인은 재확인 완료.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions