MeshService::run | error: {"stack":"HttpError: HTTP request failed
RCA: MeshService::run | HttpError 403
Overview#
What Happened#
2026-04-22 14:00:15 KST (05:00:15 UTC), cupixworks-any-mesh-agent (ap-southeast-2)에서 BIM 모델 3311의 mesh extraction 처리 중 HTTP 403 에러가 발생했다. mesh extraction 자체는 성공했으나, 결과물을 S3에 업로드하기 위한 Tesla API 호출에서 BIM이 이미 trash 상태여서 실패했다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | HttpError |
| exception.message | HTTP request failed |
| top_frame | @tesla/typescript-node-sdk/api/bimApi.js:1189 |
| env | production, ap-southeast-2 |
Timeline#
- 13:57:24 KST — BIM 3311에 대한 정상적인 API 호출 시작 (GET/PUT 200)
- 13:58:36 KST — Mesh agent가 SQS 메시지를 수신하여 BIM 3311 처리 시작
- 13:58:42 KST —
updateBimMeshState(extracting)성공 (PUT 200) - 13:59:09 KST — 사용자가 BIM 3311을 삭제 (trash):
PUT /api/v1/bims/3311/trash[204] - 14:00:10 KST — Mesh extraction 완료 (filepaths count: 16)
- 14:00:16 KST —
createResource403,createResourceUploadCredentials403 (ENT4000: Bim not found) - 14:00:17 KST —
updateBimMeshState(error)403 (ENT4000: Bim not found) - 14:00:17 KST — SQS 메시지 삭제, 다음 작업(BIM 3312)으로 정상 진행
Error Log#
MeshService::run | error: {"stack":"HttpError: HTTP request failed
at Request._callback (/tmp/agent/dist/node_modules/@tesla/typescript-node-sdk/api/bimApi.js:1189:40)
at self.callback (/tmp/agent/dist/node_modules/request/request.js:185:22)
at Request.emit (node:events:524:28)
at Request.emit (node:domain:489:12)
at Request.<anonymous> (/tmp/agent/dist/node_modules/request/request.js:1154:10)
at Request.emit (node:events:524:28)
at Request.emit (node:domain:489:12)
at IncomingMessage.<anonymous> (/tmp/agent/dist/node_modules/request/request.js:1076:12)
at Object.onceWrapper (node:events:638:28)
at IncomingMessage.emit (node:events:536:35)","message":"HTTP request failed","response":{"body":{},"statusCode":403},"body":{},"statusCode":403,"name":"HttpError"}
Impact#
- Service:
cupixworks-any-mesh-agent - Team: built
- 발생 횟수: 1
- 최초 발생: 2026-04-22T05:00:15.670Z
- 최근 발생: 2026-04-22T05:00:15.670Z
이 에러는 단일 BIM 모델(ID 3311)에만 영향을 미쳤다. mesh extraction 작업은 이미 완료되었으나 결과물이 업로드되지 않았고, mesh_state가 extracting으로 남았을 가능성이 있다 (error 상태 업데이트도 실패). 다만, BIM이 이미 trash 상태이므로 사용자에게 직접적인 영향은 없다. 후속 작업(BIM 3312, 3313 등)은 정상 처리되었다.
Root Cause Summary#
사용자가 BIM 3311을 삭제(trash)하는 동안 mesh agent가 해당 BIM의 mesh extraction을 처리하는 race condition이 발생했다. mesh agent는 13:58:36에 BIM 3311 처리를 시작하여 mesh extraction을 실행했으나, 그 사이 13:59:09에 사용자가 PUT /api/v1/bims/3311/trash를 호출하여 BIM을 삭제했다. mesh extraction이 약 1.5분 후 완료되었을 때, 업로드를 위한 Tesla API 호출에서 BIM이 trash 상태여서 Cupix::Errors::NotFound (code: ENT4000)이 발생했고, 이것이 Rails의 ClientErrorController에서 HTTP 403으로 변환되었다.
Technical Analysis#
Code Path#
- Entry point:
mesh-service.ts:101—MeshService.run(targetId) mesh-service.ts:102—createCPBimByBimId(targetId)→ BIM 3311 fetch 성공 (이 시점에는 BIM이 아직 존재)mesh-service.ts:108—updateBimMeshState(targetId, Extracting)→ mesh_state를extracting으로 업데이트 성공mesh-service.ts:112—runMeshExtractor(cpBim)→ mesh extraction 실행 (약 1.5분 소요)- 이 시점 (13:59:09)에 사용자가 BIM 3311을 trash로 이동
mesh-service.ts:115—uploadMeshArchiveFileByCPBim(cpBim)→ 업로드 시도- Failure point:
mesh-service.ts:211-227—createMeshResourceCredentials()
mesh extraction이 장시간(약 1.5분) 소요되는 동안 BIM의 상태가 변경될 수 있지만, agent는 처리 시작 시점에만 BIM 상태를 확인하고 이후 변경을 감지하지 못한다.
protected run = async (targetId: number): Promise<void> => {
const cpBim = await this.createCPBimByBimId(targetId);
if (cpBim) {
this._modelInProcess = cpBim;
this._forgeApi.updateRegionFromBim(cpBim.forgeRegion);
if (cpBim.workspaceDirPath) this.setModelWorkspace(cpBim.workspaceDirPath);
await this.updateBimMeshState(targetId, TESLA.UpdateBimRequest.MeshStateEnum.Extracting);
try {
await this.forgeAuth.authenticate();
await this.runMeshExtractor(cpBim); // ~1.5분 소요
await cpBim.createMeshArchive();
await cpBim.checkMeshFiles();
await this.uploadMeshArchiveFileByCPBim(cpBim); // 여기서 403 발생
await this.updateBimMeshState(targetId, TESLA.UpdateBimRequest.MeshStateEnum.Extracted);
} catch (error) {
logger.error('MeshService::run | error: %s', JSON.stringify(error, Object.getOwnPropertyNames(error)));
await this.updateErrorState(cpBim); // 이것도 403 발생
}
}
};
createMeshResourceCredentials에서 createResource와 createResourceUploadCredentials 모두 실패:
private createMeshResourceCredentials = (cpBim: CPBim): Promise<TESLA.UploadCredentials> => {
return this.cupixApi.bim.createResource(
cpBim.id, {
name: 'mesh.zip',
kind: 'mesh'
})
.then(() => {
return this.cupixApi.bim.createResourceUploadCredentials(cpBim.id, 'mesh');
})
.catch(() => {
// createResource 실패 시에도 upload credentials 시도
return this.cupixApi.bim.createResourceUploadCredentials(cpBim.id, 'mesh');
})
.catch(ec => {
logger.warn('MeshService::createMeshResourceByCPBim | end - error upload url');
throw ec; // 최종 실패 — 이 에러가 run()의 catch로 전파
});
};
Tesla API 서버 측에서 BIM 조회 시, trash 상태인 BIM은 permission_joins 쿼리에서 제외되어 NotFound가 발생한다:
if model.nil?
if self.where(attrs).in_trash.present?
raise Cupix::Errors::NotFound.new(code: 'ENT4000', reason: "#{current_class.name} not found")
else
raise Cupix::Errors::NotFound.new(code: 'ARG10002', reason: "#{current_class.name} not found")
end
end
Cupix::Errors::NotFound는 ClientErrorController에서 HTTP 403으로 변환된다 (보안상 존재 여부 노출 방지):
rescue_from Cupix::Errors::NotFound, with: :not_found_403_error
def not_found_403_error(exception)
raise_error(403, exception)
end
Log Evidence#
Tesla API 측 로그에서 BIM 3311의 전체 lifecycle이 확인된다:
Datadog query: service:cupixworks-api "bims/3311" from:2026-04-22T04:50:00Z to:2026-04-22T05:10:00Z
정상 처리 단계 (BIM이 아직 존재):
{"timestamp": "2026-04-22 13:58:37 KST", "status": "info", "message": "[200] PUT /api/v1/bims/3311 (Api::V1::BimsController#update)"}
{"timestamp": "2026-04-22 13:58:38 KST", "status": "info", "message": "[200] GET /api/v1/bims/3311 (Api::V1::BimsController#show)"}
{"timestamp": "2026-04-22 13:58:42 KST", "status": "info", "message": "[200] PUT /api/v1/bims/3311 (Api::V1::BimsController#update)"}
BIM 삭제 시점:
{"timestamp": "2026-04-22 13:59:09 KST", "status": "info", "message": "[204] PUT /api/v1/bims/3311/trash (Api::V1::BimsController#trash)"}
삭제 후 agent의 API 호출 실패:
{"timestamp": "2026-04-22 14:00:16 KST", "status": "info", "message": "[403] POST /api/v1/bims/3311/resources (Api::V1::BimsController#create_resource)", "error": {"reason": "Bim not found", "code": "ENT4000", "class": "Cupix::Errors::NotFound"}}
{"timestamp": "2026-04-22 14:00:16 KST", "status": "info", "message": "[403] POST /api/v1/bims/3311/resources/mesh/upload_credentials (Api::V1::BimsController#resource_upload_credentials)", "error": {"reason": "Bim not found", "code": "ENT4000", "class": "Cupix::Errors::NotFound"}}
{"timestamp": "2026-04-22 14:00:16 KST", "status": "info", "message": "[403] PUT /api/v1/bims/3311 (Api::V1::BimsController#update)", "error": {"reason": "Bim not found", "code": "ENT4000", "class": "Cupix::Errors::NotFound"}}
{"timestamp": "2026-04-22 14:00:17 KST", "status": "info", "message": "[403] PUT /api/v1/bims/3311 (Api::V1::BimsController#update)", "error": {"reason": "Bim not found", "code": "ENT4000", "class": "Cupix::Errors::NotFound"}}
Agent 측 에러 처리 로그:
Datadog query: service:cupixworks-any-mesh-agent 403 from:2026-04-22T04:50:00Z to:2026-04-22T05:10:00Z
{
"timestamp": "2026-04-22 14:00:15 KST",
"status": "error",
"message": "BaseService::handlingMessageErrors | Error and message object",
"detail": {
"statusCode": 403,
"requestUriHref": "http://api-tesla.cupix.internal/api/v1/bims/3311?fields[0]=id&fields[1]=name...",
"bodyResult": {"code": "ENT4000", "type": "Cupix::Errors::NotFound", "reason": "Bim not found"},
"modelId": 3311,
"sqsMessageId": "1c9b7676-0594-4af3-9fee-4a6213f4dd08"
}
}
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | BIM이 처리 중에 삭제(trash)되어 후속 API 호출이 403으로 실패 | API 로그에서 13:59:09에 PUT /bims/3311/trash [204] 확인. 에러 코드 ENT4000은 base_repository.rb:349에서 BIM이 in_trash일 때만 발생. 이전 API 호출(13:58:42)은 200으로 성공. |
— | Confirmed |
| H2 | Session token이 만료되어 인증 실패 | — | 동일 session(28102ceff821b0f88b61c556c32288a0f94c7793)으로 직전 BIM 3310과 직후 BIM 3312가 정상 처리. 에러 응답이 401이 아닌 403이며, 에러 코드가 ENT4000(NotFound)이지 auth 관련이 아님. |
Rejected |
| H3 | 권한 부족으로 resource upload credentials 생성 실패 | 403 status code | ENT4000은 PermissionDenied가 아닌 NotFound 에러. BIM 3310, 3312 등 다른 모델은 동일 세션으로 정상 처리. Tesla API는 보안상 NotFound도 403으로 반환 (client_error_controller.rb:27). |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
이 에러는 사용자가 BIM을 삭제하는 정상적인 운영 시나리오에서 발생하는 race condition으로, 단발성이며 서비스 전체에 영향을 미치지 않는다. 즉시 수정이 필요한 critical 이슈는 아니다.
단기 개선 (1주 이내)#
mesh-service.ts:117-120의 catch 블록에서ENT4000(BIM in trash) 또는 403 응답을 감지하여 별도 처리하는 로직 추가. BIM이 삭제된 경우error레벨이 아닌warn레벨로 로그를 남기고,updateErrorState호출을 건너뛰어야 한다 (이미 삭제된 BIM의 상태를 업데이트할 필요가 없으므로).createMeshResourceCredentials에서 첫 번째createResource호출이 403으로 실패할 경우, 두 번째createResourceUploadCredentials호출을 시도하지 않도록 에러 코드를 확인하는 로직 추가 (mesh-service.ts:220-222).
장기 개선 (재발 방지)#
- Tesla API의 BIM trash 엔드포인트에서 진행 중인 mesh extraction 작업이 있는 경우(mesh_state가
extracting) SQS 메시지를 취소하거나 agent에 알림을 보내는 메커니즘 검토. - Agent의
BaseService에서 장시간 작업 전후로 대상 모델의 상태를 재확인하는 패턴 도입 검토.
Monitoring#
- BIM trash와 동시에 agent 처리가 실패하는 빈도 추적:
service:cupixworks-any-mesh-agent status:error "ENT4000"
- Agent의 403 에러 발생 빈도 (BIM 삭제 관련):
service:cupixworks-api "bims" status:info 403 "ENT4000" "mesh"
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial