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_state가 Error로 마킹됨 (preprocessor-service.ts:101). 다른 capture는 영향 없음 (722843, 722820 동시 처리는 성공) |
Timeline#
- 2026-06-28 17:08 KST —
PreprocessorService::run | beginfor captureId 722821 (reconstruction_state: done) - 2026-06-28 17:08 KST —
PixGenieProcess::execute | begin - captureId: 722821; Pythondocker_entrypoint.py기동 - 2026-06-28 17:08–17:31 KST — feature extraction 진행 (
Extracting Features진행도 로그 다수) - 2026-06-28 17:31:34 KST — Python
extract_features.py:110에서AssertionError: Mask not found for 1249발생, Python 프로세스 exit code 1 - 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}(정상 직렬화) 두 줄 동시 기록 - 2026-06-28 17:31:35 KST —
pix_genie_state: Error업데이트, job state Stopped,PreprocessorService::run | end
Error Log#
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:37 의 JSON.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-113—code === 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:110—assert mask is not None, f"Mask not found for {vp.frame_index}"
Python 측 traceback (STDERR 에서 추출):
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 한다:
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 가 없어 {} 가 된다:
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 가 보존된다:
} 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');
}
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 쿼리:
service:cupixworks-pix-genie-preprocessor-instance "PixGenieProcess::execute"
service:cupixworks-pix-genie-preprocessor-instance "PreprocessorService::run | end"
service:cupixworks-pix-genie-preprocessor-instance "exit code"
같은 시각 (17:31:34 KST) 두 종류의 로그가 나란히 남는다 — cluster 가 잡은 빈 메시지와 함께, 인접 라인에는 진짜 원인이 보존되어 있다:
{
"timestamp": "2026-06-28 17:31:34",
"status": "error",
"message": "PixGenieProcess::execute | failed - {}"
}
{
"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):
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:37 의 JSON.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:37—JSON.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 를 넘기는 패턴도 허용된다. 기존 호출부 시그니처 (%splaceholder) 와의 정합성을 먼저 확인할 것.- 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의 rawassert는 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 키워드 기반):
service:cupixworks-pix-genie-preprocessor-instance status:error "Mask not found"
- PixGenie process 의 비정상 종료 (exit code 1) 빈도:
service:cupixworks-pix-genie-preprocessor-instance "PixGenie process failed with exit code"
- PixGenie 전처리 실패율 (성공 대비):
service:cupixworks-pix-genie-preprocessor-instance ("PixGenieProcess::execute | failed" OR "PixGenieProcess::execute | completed successfully")
- 빈 fingerprint 잔존 모니터 (fix 배포 후 0 이 되어야 함):
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.