ES /docs

PixGenieProcess::execute | failed - {}

RCA: PixGenieProcess::execute | failed - {}

Overview#

What Happened#

2026-06-28 17:31 KST에 cupixworks-pix-genie-preprocessor-instance (us-west-2)에서 capture 722821에 대한 PixGenie 전처리가 실패했다. 내부 Python 파이프라인 (extract_features.py)이 frame index 1249의 mask를 찾지 못해 AssertionError를 발생시켜 exit code 1로 종료했고, 이를 감싼 Node.js wrapper가 에러를 다시 throw했다. Datadog 클러스터에는 failed - {} 라는 빈 메시지로 잡혔는데, 이는 별개의 로깅 버그 (JSON.stringify(Error){}) 때문이다. 발생 1회.

Quick Facts#

Field Value
exception.class Error (rethrown from AssertionError: Mask not found for 1249)
exception.message PixGenie process failed with exit code 1. STDERR: ... AssertionError: Mask not found for 1249
top_frame packages/cupix-pix-genie-preprocessor-agent/src/process/pixgenie.process.ts:111 (JS) / pixgenie/scripts/extract_features.py:110 (Python)
runtime Node.js child_process + Python 3.10 (PixGenie)
env production, us-west-2

Affected Teams#

Team / Domain Error Count Impact
cana / pix-genie 1 capture 722821 (3D Map [11:04 AM])의 PixGenie 전처리 1건 실패. pix_genie_stateError로 마킹됨 (preprocessor-service.ts:101). 다른 capture는 영향 없음 (722843, 722820 동시 처리는 성공)

Timeline#

  1. 2026-06-28 17:08 KSTPreprocessorService::run | begin for captureId 722821 (reconstruction_state: done)
  2. 2026-06-28 17:08 KSTPixGenieProcess::execute | begin - captureId: 722821; Python docker_entrypoint.py 기동
  3. 2026-06-28 17:08–17:31 KST — feature extraction 진행 (Extracting Features 진행도 로그 다수)
  4. 2026-06-28 17:31:34 KST — Python extract_features.py:110에서 AssertionError: Mask not found for 1249 발생, Python 프로세스 exit code 1
  5. 2026-06-28 17:31:34 KST — JS wrapper가 Error: PixGenie process failed with exit code 1... throw → PixGenieProcess::execute | failed - {} (빈 객체 직렬화) + PreprocessorService::run | end - {stack, message} (정상 직렬화) 두 줄 동시 기록
  6. 2026-06-28 17:31:35 KSTpix_genie_state: Error 업데이트, job state Stopped, PreprocessorService::run | end

Error Log#

Datadog Logs

text
PixGenieProcess::execute | failed - {}

Impact#

  • Service: cupixworks-pix-genie-preprocessor-instance
  • Team: cana
  • 발생 횟수: 1
  • 최초 발생: 2026-06-28 17:31 KST
  • 최근 발생: 2026-06-28 17:31 KST

Root Cause Summary#

두 개의 별개 문제가 한 에러 라인에 겹쳐 보인다. (1) 실질적 원인 — PixGenie Python pipeline 의 extract_features.py:110 에 있는 assert mask is not None, f"Mask not found for {vp.frame_index}" 가 capture 722821 의 frame 1249 에 대해 fail 했다. mask preprocessing 단계 (별도 mask-work / face-body-detector) 가 해당 frame 의 mask 산출물을 만들지 못했거나, extract_features 가 mask 디렉토리에서 frame 1249 의 파일을 찾지 못한 것이 직접 원인이다. (2) 로깅 표면 — pixgenie.process.ts:37JSON.stringify(error) 가 Error 인스턴스를 {} 로 직렬화하므로 Datadog 에 도착하는 메시지에서 원인이 완전히 사라진다. 같은 시각의 PreprocessorService::run | end 라인 (stringifyError 사용) 에는 stack/message 가 보존되어 있으므로 cluster fingerprint 만 정보가 비어있는 상태다.

Technical Analysis#

Code Path#

  • Entry: preprocessor-service.ts:66 (PreprocessorService::run)
  • Manager: pix-genie-manager.ts:56 (executePixGenieProcessing)
  • Process spawn: pixgenie.process.ts:65 (spawn('python3', [PIXGENIE_SCRIPT_PATH], ...))
  • Exit handling: pixgenie.process.ts:105-113code === 0 이면 resolve, 아니면 new Error('PixGenie process failed with exit code ${code}. STDERR: ...') 로 reject
  • Failure surface in log: pixgenie.process.ts:36-39 — catch 후 JSON.stringify(error) 로 직렬화
  • Python failure point: pixgenie/scripts/extract_features.py:110assert mask is not None, f"Mask not found for {vp.frame_index}"

Python 측 traceback (STDERR 에서 추출):

pixgenie/scripts/extract_features.py:110 (Python in container, decoded from STDERR)text
Traceback (most recent call last):
  File "/tmp/lib/pixgenie/pixgenie/scripts/docker_entrypoint.py", line 159, in <module>
    main()
  File "/tmp/lib/pixgenie/pixgenie/scripts/docker_entrypoint.py", line 137, in main
    run_pipeline(
  File "/tmp/lib/pixgenie/pixgenie/scripts/run.py", line 95, in run_pipeline
    extract_features(
  File "/tmp/lib/pixgenie/pixgenie/scripts/extract_features.py", line 215, in extract_features
    save(
  File "/tmp/lib/pixgenie/pixgenie/scripts/extract_features.py", line 110, in save
    assert mask is not None, f"Mask not found for {vp.frame_index}"
AssertionError: Mask not found for 1249

JS side error producer (child process exit handler) — exit code 비 0 일 때 STDERR 를 메시지에 합쳐 reject 한다:

packages/cupix-pix-genie-preprocessor-agent/src/process/pixgenie.process.ts:105-113typescript
this._process.on('close', (code) => {
    logger.debug('PixGenieProcess | process exited with code: %d', code);

    if (code === 0) {
        resolve();
    } else {
        reject(new Error(`PixGenie process failed with exit code ${code}${stderr ? `. STDERR: ${stderr}` : ''}`));
    }
});

JS side log surface (cluster 가 잡은 라인) — Error 인스턴스를 JSON.stringify 로 직렬화하면 enumerable own property 가 없어 {} 가 된다:

packages/cupix-pix-genie-preprocessor-agent/src/process/pixgenie.process.ts:32-39typescript
try {
    await this.runPythonProcess(params);
    this.verifyOutput(params.outputDir);
    logger.info('PixGenieProcess::execute | completed successfully');
} catch (error) {
    logger.error('PixGenieProcess::execute | failed - %s', JSON.stringify(error));
    throw error;
}

비교 — 같은 패키지의 상위 레이어는 stringifyError helper 를 써서 stack/message 가 보존된다:

packages/cupix-pix-genie-preprocessor-agent/src/preprocessor-service.ts:100-106typescript
} catch (error) {
    await this.pixGenieManager.updatePixGenieState(captureId!, TESLA.PixGenieState.Error);
    logger.error('PreprocessorService::run | end - %s', stringifyError(error));
} finally {
    await this.pixGenieManager.updateJobState(jobId!, TESLA.UpdateJobRequest.StateEnum.Stopped);
    logger.info('PreprocessorService::run | end');
}
packages/base/src/util/utils.ts:10-12typescript
export const stringifyError = (error: any): string => {
    return JSON.stringify(error, Object.getOwnPropertyNames(error));
};

기대 동작: PixGenie Python pipeline 이 모든 frame 에 대해 mask 를 보유한 상태에서 feature extraction 을 수행. exit code 1 시 wrapper 가 stack/message 가 포함된 의미 있는 로그를 남김.

실제 동작: frame 1249 의 mask 가 누락된 채 extract_features 진입 → Python AssertionError → exit code 1 → JS Error rethrown → cluster fingerprint 는 failed - {} 라는 정보 없는 메시지로 형성됨.

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-pix-genie-preprocessor-instance "PixGenieProcess::execute"
text
service:cupixworks-pix-genie-preprocessor-instance "PreprocessorService::run | end"
text
service:cupixworks-pix-genie-preprocessor-instance "exit code"

같은 시각 (17:31:34 KST) 두 종류의 로그가 나란히 남는다 — cluster 가 잡은 빈 메시지와 함께, 인접 라인에는 진짜 원인이 보존되어 있다:

Datadog log 17:31:34 KST — captured by cluster (empty)text
{
  "timestamp": "2026-06-28 17:31:34",
  "status": "error",
  "message": "PixGenieProcess::execute | failed - {}"
}
Datadog log 17:31:34 KST — same incident, informative linetext
{
  "timestamp": "2026-06-28 17:31:34",
  "status": "error",
  "message": "PreprocessorService::run | end - {\"stack\":\"Error: PixGenie process failed with exit code 1. STDERR: ... AssertionError: Mask not found for 1249\\n    at ChildProcess.<anonymous> (/tmp/agent/dist/app.cjs:6655:16)\\n    at ChildProcess.emit (node:events:524:28)\\n    ...\",\"message\":\"PixGenie process failed with exit code 1. STDERR: ...\"}"
}

이번 capture 의 실행 흐름 (capture 722821):

text
17:08:32  PreprocessorService::run | loaded capture - id: 722821, name: 3D Map [11:04 AM], reconstruction_state: done, current_step: 3d_map_creation_completed
17:08:32  PixGenieProcess::execute | begin - captureId: 722821
17:08:32+ PixGenieProcess | STDERR: Extracting Features:  0%|...
... (~23분 feature extraction)
17:31:34  PixGenieProcess::execute | failed - {}        # cluster fingerprint
17:31:34  PreprocessorService::run | end - {"stack":"... AssertionError: Mask not found for 1249 ..."}
17:31:35  PreprocessorService::run | end

다른 동시 capture (722843, 722820, 723039) 는 같은 instance 에서 동일 시간대에 정상 완료 (PixGenieProcess::execute | completed successfully) — 인프라 수준 문제는 아니다.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 Python PixGenie pipeline 의 mask 산출물이 frame 1249 에 대해 누락되어 extract_features.py:110 assertion 이 실패 STDERR 에서 디코드된 traceback AssertionError: Mask not found for 1249 (extract_features.py:110), `PreprocessorService::run end` 의 stack 필드, exit code 1
H2 pixgenie.process.ts:37JSON.stringify(error) 가 Error 인스턴스에 대해 {} 를 반환하여 fingerprint 정보를 잃음 같은 시각 `PreprocessorService::run end라인은stringifyError사용으로 stack 보존, 같은 파일:50/:35도 동일 패턴,packages/base/src/util/utils.ts:10` 에 helper 가 이미 존재
H3 Python child process timeout (TIMEOUT_MS = 2h) feature extraction 23분 만에 종료, 2h 미만, 코드 경로상 timeout 은 Process timeout 메시지로 표시될 것 (pixgenie.process.ts:60) exit code 1 + STDERR 에 AssertionError 가 명확히 있음, timeout 메시지 없음 Rejected
H4 Instance-wide 인프라 장애 (메모리/디스크/네트워크) 동시간대 동일 instance 의 다른 capture (722843, 722820) 가 정상 성공 (completed successfully), STDERR 가 일반 progress + assertion 만 포함 Rejected
H5 reconstruction_state 가 미완 상태에서 PixGenie 진입했을 가능성 `PreprocessorService::run loaded capture로그에reconstruction_state: done, current_step: 3d_map_creation_completed명시. preprocessor-service.ts:86-89 의 가드가done` 만 허용

Fix Recommendation#

즉시 조치 (Critical)#

  • packages/cupix-pix-genie-preprocessor-agent/src/process/pixgenie.process.ts:37JSON.stringify(error) 를 동일 패키지의 stringifyError (@agents/base) 로 교체. 동일 파일의 pixgenie.process.ts:116 (process error 핸들러) 와 pix-genie-manager.ts:36, pix-genie-manager.ts:51 도 같은 안티패턴이므로 함께 정리 대상. 이렇게 하면 동일 fingerprint 가 다음번 발생 시 failed - Error: PixGenie process failed with exit code 1. STDERR: ... AssertionError: Mask not found for ... 형태로 잡혀서 cluster 검색만으로 원인이 드러난다. 메모리 노트 — agents codebase 의 fix logger 컨벤션에 따르면 logger 가 Error 객체를 native 처리하므로, helper 호출 대신 logger.error('msg', error) 형태로 그냥 Error 를 넘기는 패턴도 허용된다. 기존 호출부 시그니처 (%s placeholder) 와의 정합성을 먼저 확인할 것.
  • capture 722821 의 mask 산출물 디렉토리 (/tmp/workspace/capture_722821/... 내 mask 관련 경로 또는 S3 상의 mask 자산) 를 확인하여 frame 1249 가 실제로 누락되었는지, 아니면 별도 face-body-detector / mask-work 단계가 실패했는지 확인. 클러스터는 1회 발생이므로 transient 일 가능성 — 동일 capture 재처리로 우선 복구한다.

단기 개선 (1주 이내)#

  • PixGenie Python pipeline 의 mask 가드 강화extract_features.py:110 의 raw assert 는 STDERR 로만 정보가 흐르고 frame index 외 컨텍스트가 없다. mask 디렉토리 경로, 기대된 mask 파일명, 직전 mask 생성 step 의 산출물 카운트를 함께 raise 하면 운영자가 원인 (mask 생성 누락 vs 경로 mismatch vs race) 을 즉시 구분 가능.
  • mask 생성 단계와 extract_features 사이의 사전 검증 — extract_features 진입 전에 모든 frame 에 대해 mask 존재 여부를 한 번 sweep 해서, 결손 시 빠르게 fail-fast 하고 어느 frame 들이 누락되었는지 한 번에 보고. 23분 feature extraction 후 1개 frame 때문에 실패하는 시간 낭비를 막을 수 있다.
  • agents 패키지 전반의 JSON.stringify(error) 일괄 점검Grep 결과 base/util 에 stringifyError 가 이미 존재하므로, agents 하위 패키지에서 JSON.stringify(error) 또는 JSON.stringify(ec) 패턴을 grep 으로 찾아 helper 또는 logger native handling 으로 치환.

장기 개선 (재발 방지)#

  • PixGenie pipeline 의 frame-level idempotent retry — 단일 frame mask 결손으로 capture 전체가 실패하지 않도록, mask 가 없는 frame 은 skip + 사후 보고하거나 mask 재생성을 한 번 시도하는 정책 도입 (정책상 허용되는 경우에 한해).
  • agents codebase 의 error logging 표준 lint rule — JSON.stringify(error) 패턴을 ESLint custom rule 또는 no-restricted-syntax 로 금지하고 stringifyError / native logger error 만 허용. 본 인시던트 같은 정보 손실 fingerprint 가 cluster 에 다시 잡히는 것을 구조적으로 방지.

Monitoring#

  • "Mask not found" assertion 발생 빈도 (Python 측, STDERR 키워드 기반):
text
service:cupixworks-pix-genie-preprocessor-instance status:error "Mask not found"
  • PixGenie process 의 비정상 종료 (exit code 1) 빈도:
text
service:cupixworks-pix-genie-preprocessor-instance "PixGenie process failed with exit code"
  • PixGenie 전처리 실패율 (성공 대비):
text
service:cupixworks-pix-genie-preprocessor-instance ("PixGenieProcess::execute | failed" OR "PixGenieProcess::execute | completed successfully")
  • 빈 fingerprint 잔존 모니터 (fix 배포 후 0 이 되어야 함):
text
service:cupixworks-pix-genie-preprocessor-instance "PixGenieProcess::execute | failed - {}"

Risk Assessment#

  • Risk level: low (단일 capture, 1회 발생, 동시간대 다른 capture 정상)
  • 예상 복잡도: standard — JS 측 로깅 수정은 trivial, Python pipeline 의 mask 결손 진단은 mask 생성 단계 (별도 mask-work / face-body-detector) 와 entrypoint 환경 점검을 동반하므로 standard.