ES /docs

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#

  1. 13:57:24 KST — BIM 3311에 대한 정상적인 API 호출 시작 (GET/PUT 200)
  2. 13:58:36 KST — Mesh agent가 SQS 메시지를 수신하여 BIM 3311 처리 시작
  3. 13:58:42 KSTupdateBimMeshState(extracting) 성공 (PUT 200)
  4. 13:59:09 KST — 사용자가 BIM 3311을 삭제 (trash): PUT /api/v1/bims/3311/trash [204]
  5. 14:00:10 KST — Mesh extraction 완료 (filepaths count: 16)
  6. 14:00:16 KSTcreateResource 403, createResourceUploadCredentials 403 (ENT4000: Bim not found)
  7. 14:00:17 KSTupdateBimMeshState(error) 403 (ENT4000: Bim not found)
  8. 14:00:17 KST — SQS 메시지 삭제, 다음 작업(BIM 3312)으로 정상 진행

Error Log#

Datadog Logs

text
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:101MeshService.run(targetId)
  • mesh-service.ts:102createCPBimByBimId(targetId) → BIM 3311 fetch 성공 (이 시점에는 BIM이 아직 존재)
  • mesh-service.ts:108updateBimMeshState(targetId, Extracting) → mesh_state를 extracting으로 업데이트 성공
  • mesh-service.ts:112runMeshExtractor(cpBim) → mesh extraction 실행 (약 1.5분 소요)
  • 이 시점 (13:59:09)에 사용자가 BIM 3311을 trash로 이동
  • mesh-service.ts:115uploadMeshArchiveFileByCPBim(cpBim) → 업로드 시도
  • Failure point: mesh-service.ts:211-227createMeshResourceCredentials()

mesh extraction이 장시간(약 1.5분) 소요되는 동안 BIM의 상태가 변경될 수 있지만, agent는 처리 시작 시점에만 BIM 상태를 확인하고 이후 변경을 감지하지 못한다.

packages/cupix-tesla-mesh-agent/src/mesh-service.ts:101-122typescript
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에서 createResourcecreateResourceUploadCredentials 모두 실패:

packages/cupix-tesla-mesh-agent/src/mesh-service.ts:209-228typescript
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가 발생한다:

app/repositories/base_repository.rb:347-352ruby
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::NotFoundClientErrorController에서 HTTP 403으로 변환된다 (보안상 존재 여부 노출 방지):

app/controllers/concerns/client_error_controller.rb:27,55-56ruby
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이 확인된다:

text
Datadog query: service:cupixworks-api "bims/3311" from:2026-04-22T04:50:00Z to:2026-04-22T05:10:00Z

정상 처리 단계 (BIM이 아직 존재):

json
{"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 삭제 시점:

json
{"timestamp": "2026-04-22 13:59:09 KST", "status": "info", "message": "[204] PUT /api/v1/bims/3311/trash (Api::V1::BimsController#trash)"}

삭제 후 agent의 API 호출 실패:

json
{"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 측 에러 처리 로그:

text
Datadog query: service:cupixworks-any-mesh-agent 403 from:2026-04-22T04:50:00Z to:2026-04-22T05:10:00Z
json
{
  "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] 확인. 에러 코드 ENT4000base_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 ENT4000PermissionDenied가 아닌 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 처리가 실패하는 빈도 추적:
text
service:cupixworks-any-mesh-agent status:error "ENT4000"
  • Agent의 403 에러 발생 빈도 (BIM 삭제 관련):
text
service:cupixworks-api "bims" status:info 403 "ENT4000" "mesh"

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial