ES /docs

HTTP request failed

RCA: HTTP request failed

Overview#

What Happened#

2026-06-30 07:36 KST 에 cupixworks-any-room-agent 가 BIM 20436 에 대한 room 추출 작업을 처리하는 도중, 사용자가 동시에 해당 BIM 을 trash 시켜 후속 API 호출(GET /api/v1/rooms?bim_id=20436) 이 403 ENT4000 "Bim not found" 로 실패했다. tesla-v1 OpenAPI 클라이언트가 4xx 응답에 대해 던지는 HttpError("HTTP request failed") 가 그대로 SQS 메시지 핸들러로 전파되어 error 로그가 1건 기록되었다. SQS 메시지는 4xx 정책에 따라 재시도 없이 삭제되었으므로 사용자 영향은 없다.

Quick Facts#

Field Value
exception.class HttpError
exception.message HTTP request failed
top_frame cupix-api/client/openapi/typescript-node/tesla-v1/api/apis.ts:199
underlying.status 403
underlying.body {"code":"ENT4000","type":"Cupix::Errors::NotFound","reason":"Bim not found"}
request GET http://api-tesla.cupix.internal/api/v1/rooms?bim_id=20436&...
modelId 20436
env production, us-west-2

Affected Teams#

Team / Domain Error Count Impact
ctsarquitetura (room extraction) 1 사용자 영향 없음 — BIM 이 사용자 본인의 의도로 trash 처리되었고 SQS 메시지는 4xx 정책으로 즉시 삭제됨

Timeline#

  1. 2026-06-30 07:35:57 KSTcupixworks-any-room-agent 가 SQS 메시지 수신, BaseService::runByMessage | id: 20436 시작
  2. 2026-06-30 07:35:58 KST[200] GET /api/v1/bims/20436 (agent 가 BIM 메타 조회 성공)
  3. 2026-06-30 07:36:00 ~ 07:36:17 KST — agent 가 Forge SVF2 room 추출 수행, 그 사이 사용자가 BIM 편집(PUT /api/v1/bims/20436 다수)
  4. 2026-06-30 07:36:17 KST — 사용자가 PUT /api/v1/bims/20436/trash 호출, [204] 응답 (BIM 이 trashed 상태로 전환)
  5. 2026-06-30 07:36:22 KST — agent 가 추출 결과를 서버와 동기화하려고 GET /api/v1/rooms?bim_id=20436 호출 → 403 ENT4000 Bim not found, HttpError("HTTP request failed") 발생
  6. 2026-06-30 07:36:22 KSTBaseService::handlingMessageErrors 가 에러를 기록 후 SQS 메시지 삭제 (4xx 정책)
  7. 2026-06-30 07:36:24 KST — agent 의 후속 PUT /api/v1/bims/20436 (room_state 업데이트 시도) 도 403 으로 실패하고 종료

Error Log#

Datadog Logs

text
HTTP request failed

Impact#

  • Service: cupixworks-any-room-agent
  • Team: ctsarquitetura
  • 발생 횟수: 1
  • 최초 발생: 2026-06-30 07:36 KST
  • 최근 발생: 2026-06-30 07:36 KST

사용자 영향은 없다. trash 행위 자체가 사용자의 명시적 의도이며, 진행 중이던 room 추출 결과는 더 이상 의미가 없다. SQS 메시지는 4xx 응답에 대해 즉시 삭제되므로 재시도 폭주도 없다.

Root Cause Summary#

Room 추출 작업이 진행되는 약 25초의 윈도우 동안 사용자가 동일한 BIM(id=20436) 을 trash 시켰다. tesla API 의 BimRepository.show 는 기본적으로 Cyclable.visibility[:UNTRASHED] 스코프로 동작하므로, trashed BIM 에 대한 후속 GET /api/v1/rooms?bim_id=20436 호출이 Cupix::Errors::NotFound (ENT4000) 을 반환했다. 이 응답은 tesla-v1 OpenAPI 클라이언트(apis.ts:199) 에서 HttpError("HTTP request failed") 로 래핑되어 던져졌고, 호출부(RoomService::importRoomsFromBimcupixApi.room.getAll) 에서 잡히지 않은 채 BaseService::handlingMessageErrors 까지 전파되어 error 로그로 남았다. 본질적으로 모델의 lifecycle race condition 이며 코드 버그가 아니라 운영상 정상 시나리오에 가깝지만, 현재는 error 레벨로 로깅되어 alert noise 가 된다.

Technical Analysis#

Code Path#

Entry point — agent SQS message loop:

applications/agents/packages/base/src/base-service.ts:108-115typescript
try {
    await this.runByMessages();
} catch (error) {
    await this.handlingMessageErrors(error);
}
this.resetMessages();
await CPUtils.sleep(500);
await this.checkingQueue();

Room agent 메인 시퀀스 — 추출 완료 후 서버 rooms 와 diff 하기 위해 room.getAll(bim_id) 호출:

applications/agents/packages/cupix-tesla-room-agent/src/room-service.ts:380-382typescript
const serverRooms = await this.cupixApi.room.getAll(cpBim.id);
logger.info('RoomService::importRoomsFromBim | server room size: %d', serverRooms.length);

room.getAll 은 tesla-v1 OpenAPI 클라이언트의 getRooms 를 호출 (bim_id 필터):

applications/agents/packages/api/src/api/room.api.ts:24-34typescript
async getAll(bimId: number, listParams?: ListParams): Promise<TESLA.Room[]> {
    const api = await this.api();
    return paginateAll<TESLA.Room>(async (page, perPage) => {
        const res = await api.getRooms(
            Fields.RoomFields,
            bimId,
            undefined,
            listParams?.X_CUPIX_UPDATED_SINCE,
            listParams?.order_by,
            page,
            perPage,

"HTTP request failed" 문자열의 정확한 발화 지점 — tesla-v1 OpenAPI 자동 생성 클라이언트:

cupix-api/client/openapi/typescript-node/tesla-v1/api/apis.ts:197-202typescript
export class HttpError extends Error {
    constructor (public response: http.IncomingMessage, public body: any, public statusCode?: number) {
        super('HTTP request failed');
        this.name = 'HttpError';
    }
}

서버측 — 트래쉬된 BIM 은 default scope 에서 보이지 않음. RoomsController#indexRoomRepository#search 를 통해 BimRepository#show(bim_id) 를 호출하고, 이 시점에 Cupix::Errors::NotFound (ENT4000) 가 발생:

app/repositories/room_repository.rb:267-275ruby
if self.query_option.bim_id.present? || self.query_option.bim_revision_id.present?
  if self.query_option.bim_id.present?
    bim = BimRepository.new(current_user: self.current_user).show(self.query_option.bim_id, visibility: visibility, review_id: review_id)

    self.query_option.query[:bool][:must] << {
      term: {
        "bim.id": bim.id
      }
    }

에러 핸들링 — 4xx 응답은 SQS 메시지를 삭제하고 한번 error 로그를 남김:

applications/agents/packages/base/src/base-service.ts:240-275typescript
private getApiErrorToDeleteMessage = (error: any): any => {
    if (error == undefined) { ... }
    if (error.errno != undefined && error.code != undefined && error.syscall != undefined) { ... }
    const response = CPUtils.isJsonString(error) ? JSON.parse(error) : error.response;
    if (response == undefined) { ... }
    const statusCode = response.statusCode ? Number(response.statusCode) : undefined;
    ...
    if (statusCode != undefined && statusCode >= 400 && statusCode <= 500) {
        if (statusCode === 401) return;
        return errorMsg;
    }
    return;
};
applications/agents/packages/base/src/base-service.ts:290-313typescript
private handlingMessageErrors = async (error: any): Promise<void> => {
    ...
    const apiErrorObject = this.getApiErrorToDeleteMessage(error);
    if (apiErrorObject != undefined || this.checkReceiveCountToDeleteMessage()) {
        try {
            errorAndMessage.error = apiErrorObject;
            await this.deleteByMessage(this.messageInProcess);
            if (this._modelInProcess != undefined && this._modelInProcess.id > 0) await this.updateErrorState(this._modelInProcess);
        } catch (error) { ... }
    }
    logger.error('BaseService::handlingMessageErrors | Error and message object - %s', JSON.stringify(errorAndMessage));
    resetLogMeta();
};

기대 동작: 처리 도중 BIM 이 trash 되면, 후속 동기화 호출은 "이 작업은 이미 무의미함" 으로 인지하고 warn 레벨에서 조용히 종료되어야 한다. 실제 동작: 4xx 가 error 로그를 발생시키고, updateErrorState 가 호출되어 trash 직전의 BIM 상태(이미 cyclable scope 밖) 에 room_state=error 를 쓰려다 또 다시 403 으로 실패한다 (07:36:24Z PUT 로그 확인).

Log Evidence#

Datadog 쿼리:

text
service:cupixworks-any-room-agent 20436 status:(info OR warn OR error)

Agent 측 시퀀스 (UTC):

text
2026-06-29T22:35:57.669Z  info   BaseService::runByMessage | id: 20436
2026-06-29T22:36:22.692Z  warn   BaseService::getApiErrorToDeleteMessage | error msg - statusCode:403 ...bim_id=20436... ENT4000 "Bim not found"
2026-06-29T22:36:22.697Z  warn   CupixAuth::handleError | Response statusCode: 403 ... ENT4000 "Bim not found"
2026-06-29T22:36:22.799Z  error  HTTP request failed
2026-06-29T22:36:22.800Z  info   BaseService::cleanUpAnythingRelatedModel | path: /tmp/workspace/20436

핵심 에러 페이로드:

json
{
  "statusCode": 403,
  "requestUriHref": "http://api-tesla.cupix.internal/api/v1/rooms?fields%5B0%5D=id&...&bim_id=20436&page=1&per_page=100",
  "bodyResult": {
    "code": "ENT4000",
    "type": "Cupix::Errors::NotFound",
    "reason": "Bim not found",
    "message": "Bim not found"
  },
  "modelId": 20436,
  "sqsMessage": { "MessageId": "c2103e11-89e1-41a3-81db-d149f10740cc", "Attributes": { "ApproximateReceiveCount": "1" } }
}

서버 측에서 본 동일 시점의 BIM 20436 lifecycle:

text
2026-06-30 07:32:14 KST  info   Cachable::ReviewLoad | Invalidated facility review cache on create | model=Bim | model_id=20436 | facility_id=21390
2026-06-30 07:35:44 KST  info   [200] GET /api/v1/bims/20436
2026-06-30 07:35:52 KST  info   [200] PUT /api/v1/bims/20436
2026-06-30 07:35:58 KST  info   [200] GET /api/v1/bims/20436
2026-06-30 07:36:00 KST  info   [200] PUT /api/v1/bims/20436
2026-06-30 07:36:17 KST  info   [204] PUT /api/v1/bims/20436/trash      ← BIM trashed
2026-06-30 07:36:24 KST  info   [403] PUT /api/v1/bims/20436 (Api::V1::BimsController#update) — error ENT4000

trash (07:36:17) 와 agent 의 실패 호출 (07:36:22) 사이는 약 5초. 추출 작업 전체 길이는 약 25초.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 처리 중인 BIM 20436 이 사용자 trash 로 인해 mid-flight 에 사라져, 후속 GET /api/v1/rooms?bim_id=20436 가 403 ENT4000 으로 실패했고 OpenAPI 클라이언트가 HttpError("HTTP request failed") 로 래핑함 tesla API 로그: [204] PUT /api/v1/bims/20436/trash @ 07:36:17 → [403] PUT /api/v1/bims/20436 @ 07:36:24; agent 로그: runByMessage id:20436 @ 07:35:57 → HTTP request failed @ 07:36:22; RoomRepository#searchBimRepository#show 통해 untrashed scope 로 조회 (room_repository.rb:267-275); HttpError 생성 위치 확인 (apis.ts:199) Confirmed
H2 tesla API 의 일반적인 인증/권한 오류 (토큰 만료 등) 동일 시간대 동일 agent 의 [200] GET /api/v1/bims/20436 가 직전에 성공함, 401 이 아니라 403 + ENT4000 (NotFound) 임 Rejected
H3 tesla API 또는 SVF2 의 일시적 외부 장애 status-board 결과 dep:* active 인시던트 없음; 동일 시점 동일 endpoint 의 다른 호출은 200; 단일 BIM(20436) 에만 발생; 다른 시점 Failed to get rooms from svf2 로그는 별개 패턴(SVF2 manifest 이슈)이며 본 클러스터와 다름 Rejected
H4 SQS 메시지 중복 처리로 인한 stale id ApproximateReceiveCount: 1 (최초 수신); runByMessage id:20436 가 07:35:57 에 처음 시작됨; trash 가 그 후 발생 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

없음. 이 에러는 production impact 가 없고 단발성(occurrence_count=1) 이다. 운영적 정상 시나리오(사용자가 진행 중 BIM 을 trash) 에 대한 noise 로깅이므로 hot-fix 불필요.

단기 개선 (1주 이내)#

  • 로그 레벨 다운그레이드 (race condition 시나리오): applications/agents/packages/base/src/base-service.ts:311handlingMessageErrors 에서, getApiErrorToDeleteMessage 가 반환한 errorMsgbodyResult.codeENT4000 (NotFound) 인 경우 logger.error 대신 logger.warn 으로 기록하도록 분기 추가. 이미 4xx 정책으로 메시지를 삭제하고 있으므로 alert 가치는 낮다. 메모리의 "Assess error severity during RCA" 노트와 일치하는 패턴 (cross-region / NotFound mid-flight 는 warn 이 적절).
  • 호출부 선제 검증: room-service.ts:380room.getAll(cpBim.id) 호출 직전, BIM lifecycle 상태가 여전히 valid (trashed 아님) 한지 한번 더 짧게 확인하거나, 호출을 try/catch 로 감싸 ENT4000 인 경우 "BIM trashed during extraction" 메시지로 warn 종료. 그러면 BaseService 까지 에러가 올라가지 않는다.

장기 개선 (재발 방지)#

  • Job-level lifecycle precondition check: SQS 메시지 처리 시작 직후, cpBimtrashed_at 또는 state 를 확인하여 이미 trash 인 경우 즉시 메시지 삭제하고 종료하는 가드를 BaseService::run 또는 각 service run 의 입구에 추가. 현재는 createCPBimByBimId 에서 ARG10002 만 swallowing 하지만, 처리 중간의 trash 는 막을 수 없다.
  • OpenAPI 클라이언트 에러 메시지 개선 (선택): HttpError("HTTP request failed") 는 statusCode/uri 정보를 메시지에 포함하지 않아 fingerprint 가 너무 broad 하다. 클러스터링 품질을 위해 HTTP request failed: 403 GET /api/v1/rooms 처럼 식별 가능한 정보를 포함시키면 향후 분류가 쉬워진다. 단, 자동 생성 코드이므로 codegen 템플릿 변경이 필요.

Monitoring#

추가/조정 권장 메트릭 (release dashboard timeseries 위젯용 — text 코드 블록은 widget 에 그대로 들어감):

text
service:cupixworks-any-room-agent status:error "HTTP request failed"
text
service:cupixworks-any-room-agent "ENT4000" "Bim not found"
text
service:cupixworks-api @http.status_code:403 @http.url_details.path:"/api/v1/rooms"

알림 정책: 위 쿼리들이 단발(1건) 발생할 때는 알림 억제. 5분 내 동일 BIM 또는 facility 에 ≥ 3 건이 발생하면 그때 alert (실제 권한 회귀 또는 광범위 race condition 가능성).

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial (만약 단기 개선을 적용한다면 — ENT4000 분기 1개 추가)

이 인시던트는 사용자 데이터 손실, 서비스 다운, 데이터 일관성 손상 어느 쪽도 유발하지 않는다. 단순 alert noise. 즉시 코드 변경 없이 warn 다운그레이드 정도가 가장 가성비 높은 조치다.