BimRevisionService::run | error: "HTTP request failed"
RCA: BimRevisionService::run | error: "HTTP request failed"
Overview#
What Happened#
2026-06-10 16:02 KST cupixworks-any-bimrevision-agent 에서 BIM revision 24050에 대한 비교 작업을 SQS로 수신하여 처리하던 중, 부모 BIM(id=19801) 메타데이터 조회 단계에서 Tesla API가 403 Bim not found (ENT4000: Record has trashed)를 반환하면서 OpenAPI generated client가 HttpError('HTTP request failed')를 throw하여 작업이 실패했다. 사용자가 동일 BIM을 약 2분 전(15:59:58 KST)에 trash로 옮긴 직후 큐에 적재된 revision 메시지가 처리되며 발생한 race condition이다. 발생 횟수는 1건.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | HttpError (OpenAPI generated client) |
| exception.message | HTTP request failed |
| top_frame | bim-revision-service.ts:189 (catch block in BimRevisionService.run) |
| failing_call | cupixApi.bim.get(19801) → [403] GET /api/v1/bims/19801 |
| upstream_error | Cupix::Errors::NotFound (code ENT4000, "Record has trashed") |
| runtime | Node.js (cupixworks agents monorepo) |
| env | production, region us-west-2 |
| tenant | cupix |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| ikukbo (BIM revision agent) | 1 | BIM revision 24050 비교 결과가 Error 상태로 마킹됨. 사용자 가시 영향: 해당 revision의 비교/diff UI가 비어 있거나 Error 표시 (uncertain — UI 검증 필요). |
Timeline#
- 2026-06-10 15:55:16 ~ 15:59:28 KST — BIM 19801에 대해 정상적인 GET/PUT/POST 호출이 다수 발생 (translation, meta update 등).
- 2026-06-10 15:59:58 KST —
[204] PUT /api/v1/bims/19801/trash호출로 BIM이 trash 상태로 이동. - 2026-06-10 16:02:06 KST — bim-revision-agent가 SQS 메시지(
id: 24050)를 수신, BaseService runByMessage 시작. - 2026-06-10 16:02:08 KST —
BimRevisionService::run | si_trace_id: 6651d331-...로그.loadBimRevision(24050)/loadPreviousBimRevision(24049)모두 200 OK. - 2026-06-10 16:02:09 KST — Tesla API:
[200] GET /api/v1/bim_revisions/24050및[200] GET /api/v1/bim_revisions/24049. - 2026-06-10 16:02:10 KST — Tesla API:
[403] GET /api/v1/bims/19801(Bim not found,ENT4000: Record has trashed). - 2026-06-10 16:02:08-09 KST — agent:
BimRevisionService::run | error: "HTTP request failed"(catch block 진입, ec.message만 직렬화). - 2026-06-10 16:02:11 KST —
[200] PUT /api/v1/bim_revisions/24050(state: Error). SQS 메시지 삭제, workspace 정리.
Error Log#
BimRevisionService::run | error: "HTTP request failed"
Impact#
- Service:
cupixworks-any-bimrevision-agent - Team: ikukbo
- 발생 횟수: 1
- 최초 발생: 2026-06-10 16:02 KST
- 최근 발생: 2026-06-10 16:02 KST
- 사용자 영향: BIM revision 24050의
bim_comparison_state가Error로 마킹됨. 단, 부모 BIM(19801)이 trash 상태이므로 사용자는 이미 해당 BIM을 사용하지 않는 상태. 실질적 작업 손실은 없으나 retry 무한 실패가 발생할 수 있음 (uncertain — DLQ/재시도 정책 검증 필요).
Root Cause Summary#
사용자가 BIM 19801을 trash로 옮긴 직후(15:59:58 KST), 같은 BIM을 부모로 갖는 BIM revision 24050에 대한 비교 작업 SQS 메시지가 약 2분 뒤(16:02:06 KST) 큐에서 dequeue되어 처리되었다. BimRevisionService.createCPBimByCPBimRevision이 cupixApi.bim.get(19801)을 호출하자 Tesla API의 BaseRepository#show가 active scope에서는 BIM을 찾지 못하고 in_trash scope에서 찾아 Cupix::Errors::NotFound(code: 'ENT4000', reason: 'Bim not found')를 raise했다. client_error_controller가 이를 HTTP 403으로 매핑하여 응답했고, OpenAPI generated TypeScript client의 HttpError 클래스는 status code/body와 무관하게 항상 super('HTTP request failed')만으로 Error를 만들기 때문에 agent의 catch block에는 원인을 식별할 수 없는 단순 메시지만 남았다. agent에는 trashed/404/403 상황에 대한 별도 분기가 없으며, revision은 일반 에러로 간주되어 bim_comparison_state: Error로 종결되었다.
Technical Analysis#
Code Path#
Entry: SQS message → BaseService.runByMessage → BimRevisionService.run(targetId=24050, msgObject) (bim-revision-service.ts:150)
1. revision 두 건 조회 (성공) — bim-revision-service.ts:166-167
try {
if (previousTargetId === undefined || previousTargetId === null || isNaN(Number(previousTargetId))) {
logger.error('BimRevisionService::run | invalid previousTargetId: %s, msgObject: %s', previousTargetId, JSON.stringify(msgObject));
throw new Error(`Invalid previousTargetId: ${previousTargetId}`);
}
await this.loadBimRevision(targetId);
await this.loadPreviousBimRevision(previousTargetId);
두 호출 모두 [200] GET /api/v1/bim_revisions/24050, [200] GET /api/v1/bim_revisions/24049로 성공.
2. createTargetModels → bim.get (실패 지점) — bim-revision-service.ts:285 및 :313
private createTargetModels = async (cpBimRevision: CPBimRevision): Promise<void> => {
logger.debug('BimRevisionService::createTargetModels | begin - bimRevisionId: %d', cpBimRevision.id);
const cpBim = await this.createCPBimByCPBimRevision(cpBimRevision); // ← 여기서 throw
logger.debug('BimRevisionService::createTargetModels | cpBim: %s', cpBim.id);
if (cpBim.useSiteinsights) {
await this.loadAllElements(cpBim);
if (!cpBimRevision.validateDuplicatedBimExternalIds()) {
throw new Error('duplicated bim_external_ids found');
}
await this.loadAllLevels(cpBim);
}
};
private createCPBimByCPBimRevision = async (cpBimRevision: CPBimRevision): Promise<CPBim> => {
logger.debug('BimRevisionService::createCPBimByCPBimRevision | begin - bimRevisionId: %d', cpBimRevision.id);
if (!cpBimRevision.srvBim?.id) {
logger.error('BimRevisionService::createCPBimByCPBimRevision | srvBim is undefined - bimRevisionId: %d', cpBimRevision.id);
throw new Error('srvBim is undefined');
}
const srvBim = await this.cupixApi.bim.get(cpBimRevision.srvBim.id); // ← Tesla API 호출
logger.debug('BimRevisionService::createCPBimByCPBimRevision | end - bim_id: %d', srvBim.id);
return new CPBim(srvBim, cpBimRevision);
};
3. OpenAPI client가 throw하는 HttpError — cupix-api/client/openapi/typescript-node/tesla-v1/api/apis.ts:197-202
export class HttpError extends Error {
constructor (public response: http.IncomingMessage, public body: any, public statusCode?: number) {
super('HTTP request failed'); // ← message에는 status code/body가 들어가지 않음
this.name = 'HttpError';
}
}
statusCode/body는 HttpError 인스턴스에 보존되지만 super('HTTP request failed')로 인해 error.message에는 항상 동일 문자열만 남는다.
4. Tesla API: BIM이 trash 상태일 때 ENT4000 raise — tesla/app/repositories/base_repository.rb:351-356
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
rescue_from Cupix::Errors::PermissionDenied, with: :permission_denied_403_error
rescue_from Cupix::Errors::NotFound, with: :not_found_403_error
Cupix::Errors::NotFound는 controller 레벨에서 HTTP 403 으로 매핑된다 (코드 ENT4000 = "Record has trashed", tesla/config/error_code/entity.yml:49-50).
5. Failure point: catch block 메시지 직렬화 — bim-revision-service.ts:188-191
} catch (ec: any) {
logger.error('BimRevisionService::run | error: %s', JSON.stringify(ec.message));
await this.updateBimRevisionBimComparisonState(targetId, TESLA.UpdateBimRevisionRequest.BimComparisonStateEnum.Error, this.errorCode);
}
ec.message는 'HTTP request failed'이므로 status code/응답 body가 모두 사라진다. ec는 HttpError 인스턴스이므로 ec.statusCode, ec.body에는 진짜 원인이 들어 있지만 로그에는 남지 않는다.
기대 동작 vs 실제 동작:
- 기대: BIM이 trash로 이동된 경우 retry/재처리가 무의미하므로 trashed로 인지하고 SQS 메시지를 ack한 뒤 명확한 사유(
error_code: BimRevisionParentBimTrashed등)로 종결. - 실제: 일반 HTTP 실패와 구분 없이
error_code: Default로 종결되며, 로그에는 status code도 없는HTTP request failed만 남아 운영자가 사후 진단 시 Tesla API 로그와 timestamp를 별도로 매칭해야 한다.
Log Evidence#
Datadog 쿼리 (agent 측, 실패 trace):
service:cupixworks-any-bimrevision-agent "BimRevisionService"
time: 2026-06-10T06:30:00Z ~ 2026-06-10T07:30:00Z
핵심 agent 로그 (시간순):
{ "ts": "2026-06-10 16:02:08", "status": "info", "msg": "BaseService::runByMessage | id: 24050" }
{ "ts": "2026-06-10 16:02:08", "status": "info", "msg": "CupixAuth::setSession | session_id: 3b07b9fca0894313e64c9de0e177360791145182" }
{ "ts": "2026-06-10 16:02:08", "status": "info", "msg": "BimRevisionService::run | si_trace_id: 6651d331-8618-4d66-bc46-74fc88bff2ed" }
{ "ts": "2026-06-10 16:02:08", "status": "error", "msg": "BimRevisionService::run | error: \"HTTP request failed\"" }
{ "ts": "2026-06-10 16:02:09", "status": "info", "msg": "BimRevisionService::REVISE-END | {\"src_revision_id\":24050,\"prev_revision_id\":24049,\"si_trace_id\":\"6651d331-8618-4d66-bc46-74fc88bff2ed\",\"elapsed_ms\":929,\"status\":\"error\",\"error_message\":\"HTTP request failed\",\"counts\":{\"total\":0,\"modified\":0,\"removed\":0,\"exist\":0}}" }
전체 처리 시간 929ms — loadBimRevision, loadPreviousBimRevision 이후 첫 번째 bim.get에서 즉시 실패했음을 시사.
Datadog 쿼리 (Tesla API 측, BIM 19801 호출 이력):
service:cupixworks-api "bims/19801"
time: 2026-06-10T06:00:00Z ~ 2026-06-10T07:30:00Z
핵심 API 로그:
2026-06-10 15:59:28 [200] PUT /api/v1/bims/19801 (Api::V1::BimsController#update)
2026-06-10 15:59:58 [204] PUT /api/v1/bims/19801/trash (Api::V1::BimsController#trash) ← BIM trashed
2026-06-10 16:02:10 [403] GET /api/v1/bims/19801 (Api::V1::BimsController#show) ← agent의 호출
error: { "code": "ENT4000", "message": "Bim not found",
"class": "Cupix::Errors::NotFound" }
agent의 bim.get(19801)에 대해 Tesla API는 Cupix::Errors::NotFound(ENT4000)를 raise → controller가 403으로 매핑 → OpenAPI client가 HttpError('HTTP request failed')로 throw.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 부모 BIM이 trash로 이동된 직후 SQS 메시지가 처리되면서 cupixApi.bim.get(19801)이 ENT4000 (403)을 받았고, OpenAPI client가 message를 HTTP request failed로 평탄화하여 catch block까지 전달됨 |
Tesla API 로그 [204] PUT /api/v1/bims/19801/trash (15:59:58)와 [403] GET /api/v1/bims/19801 (16:02:10, code ENT4000); base_repository.rb:351-353에서 trash 상태에 대해 ENT4000 raise; client_error_controller.rb:27이 NotFound를 403으로 매핑; apis.ts:199의 super('HTTP request failed') |
— | Confirmed |
| H2 | bim-revision-agent의 인증 토큰 만료/세션 문제로 인해 일반 401/403 응답을 받음 | setSession은 16:02:08에 정상 수행됨 |
같은 세션으로 bim_revisions/24050, bim_revisions/24049 GET이 직전에 200으로 성공; 403 응답 본문에 인증 오류가 아닌 Bim not found ENT4000이 포함됨 |
Rejected |
| H3 | Tesla API 일시적 5xx/timeout으로 인한 실패 | HTTP request failed라는 generic message는 timeout과도 호환됨 |
Tesla API 측 동일 시각 동일 path에 명시적 [403] GET /api/v1/bims/19801 로그가 존재; 5xx/timeout 흔적 없음; 처리 시간 929ms로 timeout 임계 이하 |
Rejected |
| H4 | Forge URN 동일성 검증 실패(Cannot compare revisions with identical forge URN) 등 비-HTTP exception |
같은 catch block이 일반 Error도 처리 | 그 경우 ec.message는 Cannot compare ...이지 HTTP request failed가 아님 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
-
bim-revision-service.ts:188-191의 catch block 보강 —ec가 OpenAPI generatedHttpError인스턴스인지 검사하여ec.statusCode,ec.body까지 함께 logging.JSON.stringify(ec.message)한 줄로 모든 정보를 잃지 않도록 한다.- 근거: 동일 fingerprint의 다음 실패가 발생했을 때 어떤 endpoint가 어떤 status로 실패했는지 한 번에 식별 가능해야 RCA가 빨라진다. 현재는 Tesla API 로그와 si_trace_id로 매칭해야만 원인 파악 가능.
-
createCPBimByCPBimRevision(bim-revision-service.ts:305-317) — 부모 BIM trashed 분기 추가 —bim.get호출에서 403 +ENT4000응답인 경우BimRevisionParentBimTrashed같은 명시적 errorCode로 종결하고, 무한 retry되지 않도록 SQS 메시지를 ack 처리.- 근거: 사용자가 BIM을 trash로 옮긴 시점 이후 큐에 남아 있던 revision 비교 작업은 의미가 없으므로 명시적으로 무효 처리하는 것이 합리적이다.
단기 개선 (1주 이내)#
- 공통 OpenAPI client 에러 매퍼 도입 —
@agents/api패키지에HttpError를 래핑해error.message에[status] ${url}: ${body.message}형태로 재구성하는 helper를 두고, 모든 agent service의 catch block이 이를 사용하도록 한다. 동일 generic 메시지가 다른 agent들(forge-agent, complete-agent 등)에서도 빈번할 가능성이 높다 (uncertain — 확인 필요). - Datadog log facet 추가 —
error_code: ENT4000같은 BIM/revision 관련 client error response를 agent 로그 측에서도 facet으로 추출. 현재는 message string에만 묻혀 있어 검색이 어렵다.
장기 개선 (재발 방지)#
- Trash event consumption — BIM trashing이 완료되면 해당 BIM에 연관된 in-flight SQS 메시지를 무효화하는 cleanup 단계 도입 (예: trash 시 BimRevisionsService side에서
bim_comparison_state를 별도 상태로 마킹하여 agent가 일찍 분기 종료). - Idempotent retry policy — agent 측 SQS visibility timeout/redrive policy를 점검해 trashed 상태 같은 사용자 인입 변경에 대해 무한 재시도가 발생하지 않도록 검증 (uncertain — 현재 redrive policy 미확인).
Monitoring#
추가/확인할 Datadog 지표 및 검색 쿼리:
agent 에러 발생률 (timeseries):
sum:logs.hits{service:cupixworks-any-bimrevision-agent,status:error,@environment:production}.as_count()
HTTP request failed 메시지 발생률 (이번 cluster의 fingerprint 추적):
sum:logs.hits{service:cupixworks-any-bimrevision-agent,status:error,@environment:production,"HTTP request failed"}.as_count()
Tesla API에서 trash 관련 ENT4000 응답 수 (root-cause 신호):
sum:logs.hits{service:cupixworks-api,@environment:production,"ENT4000"}.as_count()
BIM revision-agent의 처리 종결 상태별 분포 (REVISE-END 패턴):
sum:logs.hits{service:cupixworks-any-bimrevision-agent,@environment:production,"REVISE-END","status:error"}.as_count()
추가 알림 권장: cupixworks-any-bimrevision-agent의 status=error 5분 이동 평균이 baseline의 N배를 초과할 경우 Slack 알림.
Risk Assessment#
- Risk level: low — 1회 발생, 사용자 데이터 손실 없음, BIM은 이미 사용자가 trash로 이동한 상태.
- 예상 복잡도: standard — fix는 catch block logging 보강 + ENT4000 분기 처리 두 곳, 영향 범위는 단일 agent.
- 재발 가능성: medium — 사용자가 trash 직후 큐에 남은 revision message가 dequeue되는 timing 문제는 언제든 다시 발생할 수 있음.