RealityCaptureManager::loadRealityCapture | end - "HTTP request failed"
RCA: RealityCaptureManager::loadRealityCapture | end - "HTTP request failed"
Overview#
What Happened#
2026-07-13 18:12~18:13 KST (약 70초 구간)에 cupixworks-any-voxel-agent (ap-southeast-2, production) 여러 호스트에서 RealityCaptureManager::loadRealityCapture 가 32건 실패했다. 로그 메시지는 auto-generated tesla-v1 API 클라이언트가 non-2xx 응답에 대해 던지는 HttpError('HTTP request failed') 를 그대로 노출한 것으로, 실제 HTTP status/response body 는 로깅되지 않아 원인을 좁힐 수 없다. 이후 BaseService::cleanUpAnythingRelatedModel | end - undefined modelDirPath warn 이 이어져 각 메시지 처리가 초기 fetch 단계에서 중단된 정황을 보여준다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | HttpError (tesla-v1 client) |
| exception.message | HTTP request failed |
| top_frame | packages/cupix-tesla-voxel-agent/src/manager/reality_capture.manager.ts:64 |
| env | production, region ap-southeast-2, tenant cupix |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
cupix (team.id 6, session f66c60830f9f...) |
대부분 (관찰된 로그 다수) | 다수 capture (예: 81780, 81782, 81783, 81786, 81796, 81814, 81824, 79457 등)의 voxel 생성이 실패하고 VoxelState.Error 로 마킹됨 |
endeavourgroup (team.id 180, session 84fd315b0927...) |
≥1 (capture 81133 등) | 같은 시간대 동일 warn 관찰. voxel 실패 개연성 있음 (uncertain — 동일 세션에서 HTTP request failed 로그가 발생했는지 별도 확인 필요) |
Timeline#
- 2026-07-13 18:09:03 KST —
Cupix::VoxelService.calculate_voxels!가error_code이 이미 설정된 capture (81844, 81849, 81851, 81854, 81858, 81859, 81864, 81867, 81868) 에 대해Cupix::Errors::Argument(ARG40000)"error state model cannot calculate voxel" 로 즉시 거절 (context; 이번 클러스터와는 다른 capture ID) - 2026-07-13 18:12:22 KST —
RealityCaptureManager::loadRealityCapture | end - "HTTP request failed"최초 발생 (first_seen) - 2026-07-13 18:13:24~32 KST — 동일 에러 다수 호스트/capture 에서 폭발적으로 반복 (32건, 약 70초)
- 2026-07-13 18:13:32~38 KST — 이어서
BaseService::cleanUpAnythingRelatedModel | end - undefined modelDirPathwarn 이 각 capture 마다 뒤따름 (동일 세션 IDs 매칭)
Error Log#
RealityCaptureManager::loadRealityCapture | end - "HTTP request failed"
Impact#
- Service:
cupixworks-any-voxel-agent - Team: cupix (일부 endeavourgroup 세션도 관찰)
- 발생 횟수: 32
- 최초 발생: 2026-07-13 18:12 KST
- 최근 발생: 2026-07-13 18:13 KST
Root Cause Summary#
RealityCaptureManager.loadRealityCapture 는 cupixApi.capture.get(id) (혹은 pointcloud.get(id)) 호출로 tesla API 에 capture 를 조회한다. 이 호출은 auto-generated tesla-v1 client 를 통해 이루어지며, non-2xx 응답 시 HttpError(response, body, response.statusCode) 로 reject 된다. HttpError 는 부모 Error 의 message 에 항상 "HTTP request failed" 만 넣고, 실제 HTTP 상태 코드와 응답 body 는 별도 property (.statusCode, .body) 로만 보관한다. 그런데 agent 의 .catch 블록은 JSON.stringify(ec.message) 만 로깅하기 때문에, 실제 실패 원인(예: 401 인증 만료, 404 리소스 없음, 5xx 서버 오류, 네트워크 타임아웃 여부)이 로그에서 완전히 소실된다. 관찰된 32건의 에러는 원인 미상이 아니라 원인을 남기지 않도록 로깅된 실패이며, 클러스터의 근본 원인은 tesla-v1 client HttpError 를 다루는 에이전트 측 에러 로깅의 결함이다. 유력한 하위 원인(status code 미상)에 대해서는 아래 Hypotheses 표에서 별도 관리한다.
Technical Analysis#
Code Path#
- Entry point:
BaseService::runByMessage→VoxelService::run(SQS 메시지 1건당 1회) - Failure point:
RealityCaptureManager::loadRealityCapture의.catch— reject 시ec.message만 로깅 - Underlying error source: tesla-v1 auto-generated
HttpError(message hard-coded 로"HTTP request failed")
loadRealityCapture = (realityCaptureId: number, realityCaptureType: string): Promise<TESLA.Capture | TESLA.Pointcloud> => new Promise((resolve, reject) => {
logger.debug('RealityCaptureManager::loadRealityCapture | begin, realityCaptureId: %d, realityCaptureType: %s', realityCaptureId, realityCaptureType);
const getRealityCapture = (realityCaptureId: number): Promise<TESLA.Capture | TESLA.Pointcloud> => {
if (realityCaptureType === 'capture') {
return this.cupixApi.capture.get(realityCaptureId);
} else if (realityCaptureType === 'pointcloud') {
return this.cupixApi.pointcloud.get(realityCaptureId);
} else {
logger.error('RealityCaptureManager::loadRealityCapture | end - invalid reality capture type: %s', realityCaptureType);
throw new Error(`Invalid reality capture type: ${realityCaptureType}`);
}
};
getRealityCapture(realityCaptureId)
.then(srvRealityCapture => {
if (!srvRealityCapture || !srvRealityCapture.id || srvRealityCapture.id !== realityCaptureId) {
logger.error('RealityCaptureManager::loadRealityCapture | end - failed to load reality capture');
return reject();
}
this._realityCaptureId = realityCaptureId;
this._realityCaptureType = realityCaptureType;
logger.debug('RealityCaptureManager::loadRealityCapture | end - reality capture id: %d, type: %s', srvRealityCapture.id, realityCaptureType);
resolve(srvRealityCapture);
})
.catch(ec => {
logger.error('RealityCaptureManager::loadRealityCapture | end - %s', JSON.stringify(ec.message));
reject(ec);
});
});
ec 는 실제로는 HttpError 인데, ec.message 만 찍기 때문에 항상 "HTTP request failed" 라는 동일 문자열이 32건 반복된다. ec.statusCode, ec.body, ec.response?.headers (특히 x-request-id) 를 함께 로깅하지 않는 것이 결함.
export class HttpError extends Error {
constructor (public response: http.IncomingMessage, public body: any, public statusCode?: number) {
super('HTTP request failed');
this.name = 'HttpError';
}
}
호출 위치:
run = async (targetId: number, msgObject?: any): Promise<void> => {
const targetType = msgObject.type ?? 'capture';
logger.debug('VoxelService::run | begin - targetId: %d, targetType: %s', targetId, targetType);
try {
const serverRealityCapture = await this.realityCaptureManager.loadRealityCapture(targetId, targetType);
const cpRealityCapture = this.realityCaptureManager.createCPRealityCapture(serverRealityCapture, targetType);
if (!DEBUG_MODE) await this.realityCaptureManager.updateVoxelState(TESLA.VoxelState.Aggregating);
// ...
} catch (error: any) {
logger.error('VoxelService::run | error', error);
if (!DEBUG_MODE) await this.realityCaptureManager.updateVoxelState(TESLA.VoxelState.Error);
}
};
VoxelService::run 의 최상위 catch 도 error 를 객체로 넘기지만, 사용하는 logger wrapper 가 Error 를 어떤 필드로 직렬화하는지 별개 문제. 이 위치에도 status/body 가 명시적으로 표시되어야 한다.
기대 동작 vs 실제 동작:
- 기대:
capture.get실패 시 로그에 HTTP status, response body, capture ID, request id 가 포함되어 원인 분석 가능. - 실제:
"HTTP request failed"만 반복. 32건이 같은 세션f66c60830f9f...에서 여러 호스트에 걸쳐 초당 여러 건씩 발생하는 정황상 특정 파라미터(예: capture ID)가 아닌 시스템-와이드 조건(인증 만료, tesla API 장애/재시작, 네트워크) 이 유력하나, 로그만으로는 결정 불가.
Log Evidence#
Datadog 쿼리 (클러스터에서 그대로 복사):
service:cupixworks-any-voxel-agent status:error @environment:production "RealityCaptureManager::loadRealityCapture"
에러 로그 원문 (raw JSON 발췌):
{
"message": "RealityCaptureManager::loadRealityCapture | end - \"HTTP request failed\"",
"status": "error",
"timestamp": "2026-07-13T09:13:32.390Z",
"attributes": {
"level": "error",
"session": { "id": "f66c60830f9f328a00169df8fc54eb26cf9d9565" },
"capture": { "id": 81824 },
"team": { "domain": "cupix", "id": 6 },
"environment": "production",
"region": "ap-southeast-2",
"user": { "id": 8, "email": "yohan.kim@cupix.com" },
"host": { "name": "ip-10-1-168-201.ap-southeast-2.compute.internal" }
}
}
follow-up warn (동일 트랜잭션 내):
{
"message": "BaseService::cleanUpAnythingRelatedModel | end - undefined modelDirPath",
"status": "warn",
"timestamp": "2026-07-13T09:13:38.454Z",
"attributes": {
"session": { "id": "f66c60830f9f328a00169df8fc54eb26cf9d9565" },
"capture": { "id": 81796 }
}
}
주변 컨텍스트 (cupixworks-api 쪽 관련 있으나 별개 원인):
service:cupixworks-api status:error @2026-07-13T09:09:03Z~09:11Z
→ "failed to calculate voxels for Capture ID: 81868, error: error state model cannot calculate voxel"
class=Capture function=calculate_voxels
이 API 에러들은 서로 다른 capture ID (81844, 81849, 81851, 81854, 81858, 81859, 81864, 81867, 81868) 에 대해 Cupix::VoxelService.calculate_voxels! 가 이미 error_code 가 있는 모델을 pre-flight 거절한 것이며, 이번 agent 클러스터의 capture ID 집합(81780, 81782, 81783, 81786, 81796, 81814, 81824, 79457, 81133 …) 과 겹치지 않아 직접적 원인은 아니다. 다만 동일 시간대에 voxel 파이프라인 부하가 몰렸다는 정황은 제공.
cupixworks-api 에서 09:11~09:14 UTC 구간에는 status:error 로그가 0건 검색되었다:
service:cupixworks-api status:error @2026-07-13T09:11:00Z~09:14:00Z
→ Found 0 logs
이는 (a) API 는 정상 응답했는데 client 측 처리에 문제가 있었거나, (b) API 가 실제로 응답했으나 성공 status (예: 401/404) 라서 API 자체는 error 로 로깅하지 않았거나, (c) 문제가 API 앞단(ALB/nginx/네트워크)에 있어 API 로그가 남지 않은 케이스 중 하나. 로그가 없으므로 확정 불가.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | Agent 의 HttpError 처리 코드가 ec.message (하드코딩된 "HTTP request failed") 만 로깅해 실패 원인을 감춘다 (로깅 결함) |
tesla-v1 HttpError(super('HTTP request failed')) 정의 (cupix-api apis.ts:197-202) + agent .catch(ec => logger.error(..., JSON.stringify(ec.message))) (reality_capture.manager.ts:63-66) — 두 코드가 결합해 status/body 손실을 필연화 |
— | Confirmed (관찰된 로그가 100% "HTTP request failed" 단일 문자열인 점이 뒷받침) |
| H2 | tesla API 가 5xx/timeout 을 반환해 실패 (인프라 장애) | agent 32건 폭발, 여러 호스트/세션에 걸쳐 있음 → 시스템-와이드 조건 | 동시각 cupixworks-api status:error 0건 (09:11~09:14 UTC). ap-southeast-2 리전만 관측. status-board 에도 dep 인시던트 없음 (svc:cupixworks-any-voxel-agent::unknown) |
Inconclusive — 로그 부족으로 확정/기각 불가 |
| H3 | 인증 세션 만료/무효로 401 이 반복 | 동일 세션 f66c60830f9f... 로 32건 집중 → 세션-스코프 조건 개연 |
세션 만료면 BaseService::authenticateByMessage 단계나 이후 다른 API 호출도 실패해야 하는데, loadEntityParameters 는 같은 시각 info 로그(entity parameters length: 4)가 남음 → 최소 일부 요청은 성공한 것으로 보임 (동일 세션이라도 서로 다른 message loop 이라 재확인 필요) |
Rejected (부분 성공 근거) — 단, uncertain, 확정하려면 debug/silly 로그 필요 |
| H4 | Upstream tesla API 가 pre-flight 에러(ARG40000) 로 이 capture 들을 거절 |
09:09~09:11 UTC 에 Cupix::VoxelService.calculate_voxels! 가 ARG40000 로 다수 capture 거절 |
거절된 capture ID (81844, 81849, 81851, 81854, 81858, 81859, 81864, 81867, 81868) 와 agent 에러 capture ID (81780, 81782, 81783, 81786, 81796, 81814, 81824, 79457) 가 완전히 disjoint. 또한 calculate_voxels! 의 pre-flight 거절은 애초에 SQS 큐에 메시지를 넣지 않으므로 agent 까지 도달하지 않음 |
Rejected |
| H5 | srvRealityCapture.id !== realityCaptureId mismatch 로 인한 reject (같은 함수의 다른 branch, reject() no-arg) |
이 branch 는 .catch 에 들어가지 않고 위에서 별도 logger.error('...failed to load reality capture') 를 남김 |
관찰된 로그는 end - "HTTP request failed" 이지 end - failed to load reality capture 가 아님 → 이 branch 는 발생하지 않음 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 파일:
applications/agents/packages/cupix-tesla-voxel-agent/src/manager/reality_capture.manager.ts:63-66접근:.catch(ec => ...)블록에서ec.message뿐 아니라ec.statusCode,ec.body(또는ec.response?.statusCode,ec.response?.headers?.['x-request-id']) 를 함께 로깅하도록 확장. tesla-v1 auto-generatedHttpError는 message 를 하드코딩 ("HTTP request failed") 하므로 message 만 남기면 원인 소실이 필연이다. 방향만 지시 — 구현은 team 컨벤션(cupixApi 다른 매니저의.catch패턴)을 grep 해서 맞출 것. - 파일:
applications/agents/packages/cupix-tesla-voxel-agent/src/voxel-service.ts:63-66접근: 최상위catch (error)로그도error.statusCode/error.body를 명시적으로 포함하도록 보강 (현재logger.error('...error', error)만으로는 logger wrapper 에서 어떤 필드가 직렬화되는지 불투명).
단기 개선 (1주 이내)#
- 소급 재조사 트리거: 현 클러스터의 실제 status code 를 확인할 수 있는 유일한 방법은 debug 로그 재생 또는 재발 시 로깅 개선 반영 후 재수집이다. 개선 반영 후 최소 1주 관찰하고, 유의 status code(5xx / 401) 가 나오면 별도 후속 RCA.
- 에이전트 공통 유틸: cupix-tesla-*-agent 다수 매니저가 동일 tesla-v1 client 를 쓰므로, HttpError 를 정형 문자열(
status={code} body={truncated})로 포맷팅해 로깅하는 헬퍼를@agents/utils에 추가하고 여러 매니저 catch 지점을 그것으로 대체. - 재시도 정책 검토:
capture.get은 참조 조회이므로 5xx/타임아웃에 대해 짧은 exponential backoff 재시도(최대 2회) 도입 여지 검토. 4xx 는 재시도 대상 아님.
장기 개선 (재발 방지)#
- tesla-v1 auto-generated client 를 커스터마이즈하거나 wrapper 를 두어
HttpError.message에 status/reason phrase 를 포함시키는 방향 검토 (cupix-api의 openapi 템플릿 수정 필요). 모든 agent 를 개별 수정하는 비용을 낮춘다. RealityCaptureManager::loadRealityCapture에서 no-argreject()(line 56) 는 downstream 에서undefined로 catch 되어 로깅이 더 열악해진다 — 명시적 Error 객체로 reject 하도록 통일 (사후 관찰용, 이번 이슈와 별개 개선점).
Monitoring#
service:cupixworks-any-voxel-agent status:error "RealityCaptureManager::loadRealityCapture"
service:cupixworks-any-voxel-agent status:error "HTTP request failed"
service:cupixworks-any-voxel-agent status:error @region:ap-southeast-2
로깅 개선 후에는 status code (@statusCode:5*, @statusCode:401) 등으로 분해된 쿼리를 추가할 것.
Risk Assessment#
- Risk level: medium (32건이 70초 안에 다수 capture 에 걸쳐 발생 → 여러 팀 사용자의 voxel 생성이 실패한 사용자-영향 이슈. 다만 재발/지속성은 이 시점 데이터로는 확정 불가)
- 예상 복잡도: trivial (catch 블록에서 로깅 필드 추가 — 1-3줄 변경). 단, 소급 원인 확정은 재발 대기 필요.