Failed to execute densemapper for capture ID: 33815 - error: {}
RCA: Failed to execute densemapper for capture ID: 33815 - error: {}
Overview#
What Happened#
2026-04-20 23:13 UTC에 cupixworks-capture-3dreconstruction-instance 서비스에서 capture 33815의 densemapper 실행이 실패했다. densemapper 네이티브 바이너리가 약 67분간 실행된 후 exit code 1로 종료되었으며, 에러 객체 직렬화 버그(JSON.stringify(new Error(...)) → {})로 인해 실제 에러 메시지가 로그에서 손실되었다.
Quick Facts#
| Field | Value |
|---|---|
| exception.message | Failed to execute densemapper for capture ID: 33815 - error: {} |
| top_frame | app.cjs:8318 (ThreeDReconstruction.runThreeDReconstruction) |
| env | production, eu-central-1 |
Timeline#
- 22:06:31 UTC — Job 90769 초기화, capture 33815 로드 시작 (video 1개, cluster 1개)
- 22:06:48 UTC — densemapper 프로세스 실행 시작 (
domain: innovo, region: us-west-2) - 23:13:59.288 UTC — densemapper child process가 exit code 1로 종료
- 23:13:59.289 UTC —
ThreeDReconstruction::runThreeDReconstructioncatch 블록에서 에러 포착,JSON.stringify(error)→{} - 23:13:59.290 UTC — "Failed to execute densemapper" 에러 로그 기록
- 23:13:59.696 UTC — 서비스 10초 후 강제 종료 예약
Error Log#
Failed to execute densemapper for capture ID: 33815 - error: {}
Impact#
- Service:
cupixworks-capture-3dreconstruction-instance - Team: innovo
- 발생 횟수: 1
- 최초 발생: 2026-04-20T23:13:59.290Z
- 최근 발생: 2026-04-20T23:13:59.290Z
동일 시간대에 capture 683809 (lemartec 팀, us-west-2)에서도 같은 패턴의 densemapper 실패가 발생하여 총 2건의 유사 에러가 확인되었다. 3D reconstruction 처리가 실패하면 해당 capture의 point cloud 생성이 완료되지 않아 사용자가 3D 결과물을 볼 수 없다.
Root Cause Summary#
densemapper 네이티브 바이너리가 약 67분간 실행 후 exit code 1로 비정상 종료되었다. 실제 crash 원인은 densemapper 바이너리 내부에 있으나, Node.js 측 에러 전파 과정에서 JSON.stringify(error)로 Error 객체를 직렬화할 때 빈 객체 {}가 출력되어 진단 정보가 완전히 손실되었다. Error 객체의 message, stack, name 프로퍼티는 non-enumerable이므로 JSON.stringify()로 직렬화하면 빈 객체가 반환된다. 이 직렬화 버그가 근본 원인 파악을 불가능하게 만든 직접적인 원인이다.
Technical Analysis#
Code Path#
- Entry point:
three-d-reconstruction-service.ts:57(ThreeDReconstruction.init()) - 실행 흐름:
init()→run()→runThreeDReconstruction()→ThreeDReconstructorManager.execute()→ChildProcessManager.execute()→ fork된 child process에서ThreeDReconstructionProcess.execute()→child_process.spawn()으로 densemapper 셸 스크립트 실행
1단계: densemapper child process 실행 및 실패
ThreeDReconstructionProcess는 densemapper 바이너리를 셸 명령으로 spawn한다. 프로세스가 exit code 1로 종료되면 Error 객체를 생성하여 reject한다:
_spawn.on('exit', (code, signal) => {
if (code === null) {
const errorMessage = `Process terminated unexpectedly by signal: ${signal}`;
this.log(`ThreeDReconstructionProcess::execute | ${errorMessage}`);
reject(new Error(errorMessage));
} else if (code !== 0) {
const errorMessage = `Process terminated with non-zero code: ${code}`;
this.log(`ThreeDReconstructionProcess::execute | ${errorMessage}`);
reject(new Error(errorMessage));
} else {
this.log('ThreeDReconstructionProcess::execute | 3D Reconstruction processing completed successfully');
resolve({ success: true });
}
});
2단계: BaseProcess에서 에러 메시지 추출 후 IPC 전송
BaseProcess.sendError()는 error.message만 추출하여 parent process에 전송한다:
protected sendError(id: string, error: unknown): void {
const errorMessage = error instanceof Error ? error.message : String(error);
this.sendResponse(id, '', false, undefined, errorMessage);
}
3단계: ChildProcessManager에서 새 Error 객체 생성
parent의 ChildProcessManager.handleMessage()는 IPC로 받은 response.error 문자열로 새로운 Error 객체를 생성한다:
if (response.success) {
pending.resolve(response.data);
} else {
pending.reject(new Error(response.error || 'Unknown error'));
}
4단계: JSON.stringify(error) → {} (Failure point)
runThreeDReconstruction의 catch 블록에서 JSON.stringify(error)를 사용하여 에러를 로깅한다. JavaScript의 Error 객체는 message, stack, name 프로퍼티가 non-enumerable이므로 JSON.stringify()는 {}를 반환한다:
} catch (error: any) {
logger.error(`ThreeDReconstruction::runThreeDReconstruction | Capture ID: ${cpCapture.id} | Error: %s`, JSON.stringify(error));
this.jobManager.setErrorCode(ErrorCode.Densemapper.Execute);
throw new Error(`Failed to execute densemapper for capture ID: ${cpCapture.id} - error: ${JSON.stringify(error)}`);
}
기대 동작: error.message 값인 "Process terminated with non-zero code: 1"이 로그에 기록되어야 한다.
실제 동작: JSON.stringify(error) → "{}" 출력, 에러 메시지 손실.
Log Evidence#
Datadog 검색 쿼리:
service:cupixworks-capture-3dreconstruction-instance status:error @environment:production "33815"
capture 33815의 전체 타임라인 (13개 로그):
22:06:31.276 [info] ThreeDReconstruction::init
22:06:31.276 [info] ThreeDReconstruction::authenticate | begin
22:06:31.332 [info] CupixAuth::setSession | session_id: 5e55e307...
22:06:31.333 [info] ThreeDReconstruction::authenticate | end
22:06:31.333 [info] ThreeDReconstruction::run | begin
22:06:31.334 [info] JobManager::loadJob | begin - job id: 90769
22:06:31.382 [info] JobManager::loadJob | end - job id: 90769
22:06:31.783 [info] ThreeDReconstruction::loadVideos | video count: 1
22:06:31.841 [info] ThreeDReconstruction::loadClusters | cluster count: 1
22:06:48.167 [info] ThreeDReconstruction::runThreeDReconstruction environments | domain: innovo, envName: production, launchMode: CUPIXWORKS, region: us-west-2, userEmail: undefined
23:13:59.288 [error] Process terminated with non-zero code: 1
23:13:59.289 [error] ThreeDReconstruction::runThreeDReconstruction | Capture ID: 33815 | Error: {}
23:13:59.290 [error] Failed to execute densemapper for capture ID: 33815 - error: {}
22:06:48부터 23:13:59까지 약 67분간의 공백 — densemapper 바이너리가 실행 중이었으며 중간 로그 없음. 바이너리 stdout/stderr 출력은 child process의 this.log() 호출로 전달되지만, 해당 시간대에 로그가 없어 densemapper가 silent하게 크래시한 것으로 확인.
동일 패턴의 densemapper 실패 (다른 capture):
service:cupixworks-capture-3dreconstruction-instance status:error "densemapper"
21:14:10.349 [error] Failed to execute densemapper for capture ID: 683809 - error: {} (lemartec, us-west-2, job 1024963)
23:13:59.290 [error] Failed to execute densemapper for capture ID: 33815 - error: {} (innovo, eu-central-1, job 90769)
두 건 모두 동일한 stack trace (ThreeDReconstruction.runThreeDReconstruction at app.cjs:8318)와 동일한 빈 에러 객체 패턴.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | densemapper 바이너리 내부 크래시 (메모리, 입력 데이터 문제 등) | 67분 실행 후 exit code 1 종료, 다른 capture(683809)에서도 동일 패턴 발생 | densemapper 바이너리 소스 접근 불가, stderr 출력 없음으로 구체적 원인 특정 불가 | Confirmed |
| H2 | JSON.stringify(error) 직렬화 버그로 에러 정보 손실 |
three-d-reconstruction-service.ts:322에서 JSON.stringify(error) 사용 확인, JS Error 객체의 non-enumerable 특성상 {} 출력 |
— | Confirmed |
| H3 | 네트워크/API 타임아웃으로 인한 실패 | 같은 시간대 capture 33814에서 ETIMEDOUT 발생 |
densemapper는 로컬 바이너리 실행이므로 네트워크 의존성 낮음, exit code 1은 프로세스 자체 실패를 의미 | Rejected |
| H4 | 리전 미스매치 (eu-central-1 인스턴스에서 us-west-2 설정) | 로그에서 region: us-west-2 확인, 실제 인스턴스는 eu-central-1 |
region은 환경 변수(AWS_REGION)에서 읽는 값으로 API endpoint 설정에 사용됨, densemapper 바이너리 실행 자체에는 영향 없을 가능성 높음 |
Inconclusive |
Fix Recommendation#
즉시 조치 (Critical)#
three-d-reconstruction-service.ts:322에서JSON.stringify(error)대신error instanceof Error ? error.message : String(error)또는error?.message ?? JSON.stringify(error)패턴으로 변경하여 에러 메시지가 정상적으로 로깅되도록 수정- 동일 파일 324번 줄의 throw 문에서도 같은 패턴 적용
단기 개선 (1주 이내)#
ThreeDReconstructionProcess.execute()(three-d-reconstruction.process.ts:59-68)에서 densemapper 프로세스의 stderr 출력을 캡처하여 에러 메시지에 포함하도록 개선. 현재 stderr는this.log()로 전달되지만 exit 이벤트의 에러 메시지에는 포함되지 않음- densemapper 프로세스 stdout/stderr를 별도 버퍼에 수집하여 exit code가 0이 아닐 때 마지막 N줄을 에러 메시지에 첨부하는 방식 검토
장기 개선 (재발 방지)#
- 코드베이스 전체에서
JSON.stringify(error)패턴 사용처를 검색하여 동일한 직렬화 버그가 있는 곳을 일괄 수정 - 공통 에러 직렬화 유틸리티 함수를
@agents/utils에 추가하여Error객체를 안전하게 문자열화하는 표준 패턴 제공
Monitoring#
- densemapper 실패율 모니터링:
service:cupixworks-capture-3dreconstruction-instance status:error "Failed to execute densemapper"
- 빈 에러 객체 패턴 감지:
service:cupixworks-capture-3dreconstruction-instance "error: {}"
Risk Assessment#
- Risk level: medium
- 예상 복잡도: trivial (에러 직렬화 수정은 1-2줄 변경, 단 densemapper 바이너리 자체의 크래시 원인은 별도 조사 필요)