BaseService::handlingMessageErrors | Error and message object - {"error":{"statusCode":403,"requestU
RCA: BaseService::handlingMessageErrors | 403 Bim not found
Overview#
What Happened#
2026-04-22 05:00:15 UTC (KST 14:00:15)에 cupixworks-any-mesh-agent 서비스가 BIM 3311의 mesh extraction 처리 중 403 Forbidden 에러를 발생시켰다. 에이전트가 SQS 메시지를 수신하여 BIM 데이터를 성공적으로 조회한 후, 약 90초 뒤에 다른 프로세스가 해당 BIM을 trash 처리했고, 이후 에이전트의 후속 API 호출이 모두 실패했다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | HttpError |
| exception.message | HTTP request failed (statusCode: 403, Bim not found, ENT4000) |
| top_frame | @tesla/typescript-node-sdk/api/bimApi.js:1189 |
| env | production, ap-southeast-2 |
Timeline#
- 13:58:34 KST — BIM 3311에 대한 정상적인 API 호출 시작 (PUT update 등)
- 13:58:36 KST — Mesh agent가 SQS 메시지 수신,
BaseService::runByMessage | id: 3311로그 - 13:58:38 KST — Agent가
GET /api/v1/bims/3311호출, 200 OK 응답 수신 - 13:58:42 KST — Agent가
PUT /api/v1/bims/3311(mesh_state update), 200 OK - 13:59:09 KST — BIM 3311이 trash 처리됨 (
PUT /api/v1/bims/3311/trash, 204 응답) - 14:00:15-14:00:17 KST — Agent의 후속 API 호출 실패 (403 ENT4000 "Bim not found")
- 14:00:15 KST —
BaseService::handlingMessageErrors에서 에러 로깅, SQS 메시지 삭제
Error Log#
BaseService::handlingMessageErrors | Error and message object - {"error":{"statusCode":403,"requestUriHref":"http://api-tesla.cupix.internal/api/v1/bims/3311?fields%5B0%5D=id&fields%5B1%5D=name&fields%5B2%5D=state&fields%5B3%5D=mesh_state&fields%5B4%5D=facility&fields%5B5%5D=forge_urn&fields%5B6%5D=forge_format_type&fields%5B7%5D=forge_region&fields%5B8%5D=grid_system_state&fields%5B9%5D=grid_system_upload_url&fields%5B10%5D=bim_source&fields%5B11%5D=meta&fields%5B12%5D=facility&fields%5B13%5D=bim_objects_url&fields%5B14%5D=origin&fields%5B15%5D=offset&fields%5B16%5D=rotate_z&fields%5B17%5D=transform_order&fields%5B18%5D=fresh_state&fields%5B19%5D=bim_objects_url&fields%5B20%5D=created_at&fields%5B21%5D=published_at&fields%5B22%5D=resource_state&fields%5B23%5D=forge_state&fields%5B24%5D=room_state&fields%5B25%5D=encoded_forge_urn&fields%5B26%5D=forge_workflow_id&fields%5B27%5D=last_bim_revision&fields%5B28%5D=thumbnail_urls&fields%5B29%5D=siteinsights_entities_created_at","bodyResult":{"code":"ENT4000","type":"Cupix::Errors::NotFound","reason":"Bim not found","message":"Bim not found"},"modelId":3311},"sqsMessage":{"MessageId":"1c9b7676-0594-4af3-9fee-4a6213f4dd08","Attributes":{"ApproximateReceiveCount":"1"}}}
Impact#
- Service:
cupixworks-any-mesh-agent - Team: built
- 발생 횟수: 1
- 최초 발생: 2026-04-22T05:00:15.781Z
- 최근 발생: 2026-04-22T05:00:15.781Z
BIM 3311의 mesh extraction 작업이 실패했다. 다만 BIM이 이미 trash 처리되었으므로 사용자에게 실질적인 기능 영향은 없다. SQS 메시지는 정상적으로 삭제되어 무한 재시도는 발생하지 않았다.
Root Cause Summary#
Mesh agent가 SQS 메시지를 수신하여 BIM 3311의 mesh extraction을 시작한 후, 처리 도중 (약 90초 후) 사용자 또는 다른 프로세스가 해당 BIM을 trash 처리했다. Tesla API는 trashed된 리소스에 대해 visibility_scope(UNTRASHED) 필터로 인해 Cupix::Errors::NotFound (code: ENT4000)를 발생시키며, 이는 보안 정책상 403 Forbidden으로 반환된다. Mesh agent의 createCPBimByBimId에서 error code ARG10002(리소스 미존재)만 graceful하게 처리하고, ENT4000(trashed 리소스)은 처리하지 않아 에러로 전파되었다.
Technical Analysis#
Code Path#
1. Entry point — SQS 메시지 수신 및 처리 시작
protected runByMessage = async (message: AWS.SQS.Message) => {
this._messageInProcess = message;
const messageBody = message.Body;
if (messageBody && CPUtils.isJsonString(messageBody)) {
const msgObject = JSON.parse(messageBody);
const targetId = Environment.DEBUG_MODE && Environment.CPX_MODEL_ID
? Environment.CPX_MODEL_ID
: (msgObject.id ?? msgObject.model?.id);
// ...
logger.info('BaseService::runByMessage | id: %d', targetId);
await this.authenticateByMessage(msgObject);
await this.run(targetId, msgObject); // MeshService.run() 호출
await this.cleanUpAnythingRelatedModel();
await this.deleteByMessage(message);
}
};
Agent가 BIM ID 3311로 run() 호출. 이 시점에서 BIM은 아직 정상 상태.
2. MeshService::run — BIM 조회 및 mesh extraction 시작
protected run = async (targetId: number): Promise<void> => {
const cpBim = await this.createCPBimByBimId(targetId);
if (cpBim) {
this._modelInProcess = cpBim;
this._forgeApi.updateRegionFromBim(cpBim.forgeRegion);
await this.updateBimMeshState(targetId, TESLA.UpdateBimRequest.MeshStateEnum.Extracting);
try {
await this.forgeAuth.authenticate();
await this.runMeshExtractor(cpBim); // child process 실행
await cpBim.createMeshArchive();
await cpBim.checkMeshFiles();
await this.uploadMeshArchiveFileByCPBim(cpBim); // 여기서 upload credential 요청 시 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);
}
}
};
createCPBimByBimId(3311) 호출 시 (13:58:38 KST) BIM이 정상이므로 200 OK 수신. 이후 mesh extraction child process (runMeshExtractor) 실행 중 13:59:09 KST에 BIM이 trash 처리됨. 이후 uploadMeshArchiveFileByCPBim 또는 후속 API 호출에서 403 발생.
3. Failure point — createCPBimByBimId의 에러 코드 분기
private createCPBimByBimId = (bimId: number): Promise<CPBim | undefined> =>
new Promise((resolve, reject) => {
this.cupixApi.bim.get(bimId)
.then(resBim => {
resolve(new CPBim(resBim));
})
.catch(ec => {
const body = ec?.response?.body;
if (body) {
const code = body.result?.code;
const reason = body.result?.reason ? body.result.reason : body.result?.message;
logger.warn('MeshService::createCPBimByBimId | end - reason: %s', reason);
if (code === 'ARG10002') return resolve(undefined); // 리소스 미존재만 graceful 처리
}
reject(ec); // ENT4000 (trashed)은 여기로 — reject 발생
});
});
ARG10002 (리소스가 DB에 존재하지 않음)만 resolve(undefined)로 graceful 처리하고, ENT4000 (trashed된 리소스)은 reject(ec)로 에러 전파. 다만 이 에러는 초기 GET이 아닌 후속 API 호출(upload_credentials, update 등)에서 발생했다.
4. Tesla API의 403 반환 로직
rescue_from Cupix::Errors::NotFound, with: :not_found_403_error
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
BIM 3311이 trash 상태이므로 visibility_scope(UNTRASHED) 필터에서 제외되고, in_trash 확인에서 true → ENT4000 코드로 NotFound 발생 → 403 반환.
5. Error handler — SQS 메시지 삭제 결정
if (statusCode != undefined && statusCode >= 400 && statusCode <= 500) {
if (statusCode === 401) return; // 401만 재시도
return errorMsg; // 403 포함 나머지 4xx → 메시지 삭제
}
403은 4xx 범위이므로 getApiErrorToDeleteMessage가 errorMsg를 반환 → SQS 메시지 삭제. 재시도 없이 종료.
Log Evidence#
Datadog에서 BIM 3311 관련 API 로그를 시간순으로 조회:
service:cupixworks-api "bims/3311"
Time range: 2026-04-22T04:00:00Z to 2026-04-22T06:00:00Z
핵심 이벤트:
{"timestamp": "2026-04-22 13:58:38 KST", "status": "info", "message": "[200] GET /api/v1/bims/3311 (Api::V1::BimsController#show)"}
Agent의 초기 BIM 조회 — 정상 응답.
{"timestamp": "2026-04-22 13:59:09 KST", "status": "info", "message": "[204] PUT /api/v1/bims/3311/trash (Api::V1::BimsController#trash)"}
BIM 3311 trash 처리 — agent 처리 도중 발생. 이것이 race condition의 trigger.
{"timestamp": "2026-04-22 14:00:15 KST", "status": "error", "message": "MeshService::run | error: {\"stack\":\"HttpError: HTTP request failed\\n at Request._callback (/tmp/agent/dist/node_modules/@tesla/typescript-node-sdk/api/bimApi.js:1189:40)...\",\"message\":\"HTTP request failed\",\"statusCode\":403}"}
Agent의 후속 API 호출 실패.
{"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": {"code": "ENT4000", "class": "Cupix::Errors::NotFound"}}
Upload credential 요청이 403으로 실패 — BIM이 trash 상태이므로 ENT4000.
{"timestamp": "2026-04-22 14:00:15 KST", "status": "warn", "message": "BaseService::getApiErrorToDeleteMessage | error msg - {\"statusCode\":403,...,\"bodyResult\":{\"code\":\"ENT4000\",...},\"modelId\":3311}"}
Error handler가 403을 감지하고 메시지 삭제 결정.
Agent 측 전체 흐름:
service:cupixworks-any-mesh-agent "3311"
{"timestamp": "2026-04-22 13:58:36 KST", "status": "info", "message": "BaseService::runByMessage | id: 3311"}
{"timestamp": "2026-04-22 14:00:15 KST", "status": "warn", "message": "BaseService::getApiErrorToDeleteMessage | error msg - {...\"statusCode\":403,...\"code\":\"ENT4000\"...}"}
{"timestamp": "2026-04-22 14:00:15 KST", "status": "error", "message": "BaseService::handlingMessageErrors | Error and message object - {...}"}
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | BIM 3311이 agent 처리 도중 trash되어 race condition 발생 | API 로그에서 13:59:09에 PUT /bims/3311/trash (204) 확인. 이후 모든 API 호출이 ENT4000으로 실패. Agent는 13:58:36에 처리 시작, 13:58:38에 GET 성공(200). |
— | Confirmed |
| H2 | Agent의 인증 토큰 만료로 403 발생 | 403 응답 코드가 일치 | 에러 body의 code가 ENT4000(NotFound)이지 AUTH 계열이 아님. 같은 시간대 다른 agent 작업은 정상. 401이 아닌 403이며, Tesla API는 인증 실패 시 401을 반환. |
Rejected |
| H3 | BIM 3311이 원래 존재하지 않는 리소스 | 403 상태 코드 일치 | API 로그에서 13:58:34-13:58:42에 BIM 3311에 대한 여러 성공적인 API 호출(200) 확인. 에러 코드가 ARG10002(미존재)가 아닌 ENT4000(trashed). |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
없음. 이 에러는 BIM이 정당하게 trash 처리된 후 발생한 것으로, 사용자에게 실질적 영향이 없다. SQS 메시지도 정상 삭제되어 무한 재시도가 발생하지 않는다.
단기 개선 (1주 이내)#
mesh-service.ts:131-149:createCPBimByBimId의 catch 블록에서ENT4000코드도ARG10002와 동일하게resolve(undefined)로 처리. Trashed된 BIM에 대해서는 mesh extraction을 건너뛰고 graceful하게 종료해야 한다.mesh-service.ts:101-122:run()메서드의 후속 API 호출(uploadMeshArchiveFileByCPBim,updateBimMeshState) 실패 시에도ENT4000을 감지하여 error state update 없이 조기 종료하는 로직 추가.- 로그 레벨 조정 검토: Trashed BIM에 대한 403은 예상 가능한 운영 시나리오이므로
error가 아닌warn수준이 적절할 수 있다.BaseService::handlingMessageErrors에서ENT4000응답을 별도 분기하여 warn으로 로깅.
장기 개선 (재발 방지)#
- Optimistic locking 또는 상태 체크: Agent가 장기 처리 작업 전에 BIM 상태를 재확인하는 패턴 도입. 예: mesh extraction 완료 후, upload 전에 BIM 상태를 다시 조회.
- SQS 메시지에 상태 정보 포함: SQS 메시지 발행 시 BIM의 현재 상태를 포함하여, agent가 처리 시작 시 이미 trashed된 BIM을 즉시 건너뛸 수 있도록 개선.
Monitoring#
현재 발생 빈도가 1건으로 낮아 긴급 모니터링은 불필요하다. 단, ENT4000 graceful 처리가 구현된 후 에러 감소를 확인하기 위해:
service:cupixworks-any-mesh-agent status:error "ENT4000"
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial —
createCPBimByBimId의 catch 블록에ENT4000조건 추가만으로 해결 가능