BaseService::handlingMessageErrors | Error and message object - {"error":{"statusCode":403,"requestU
RCA: BaseService::handlingMessageErrors — Floorplan not found (403)
Overview#
What Happened#
cupix-tesla-floorplan-agent (Datadog service cupixworks-any-floorplan-agent)가 SQS 메시지로 floorplan 89318을 정상적으로 처리하던 중, 마지막 단계인 PUT /api/v1/floorplans/89318/check_uploading 호출에서 HTTP 403 (code:"ARG10002", Cupix::Errors::NotFound, reason:"Floorplan not found") 응답을 받고 종료했다. Kibana로 확인한 결과 해당 floorplan은 처리 중간(15:36:38Z)에 사용자에 의해 trash 처리된 상태였고, agent의 마지막 API 호출 시점(15:36:57Z)에는 이미 untrashed scope 에 없었던 것이 원인이다. 1건의 단발성 이벤트이며 데이터 손상 없음.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Cupix::Errors::NotFound (server side) |
| exception.message | Floorplan not found |
| top_frame | packages/base/src/base-service.ts:311 (BaseService::handlingMessageErrors) |
| upstream_call | PUT http://api-tesla.cupix.internal/api/v1/floorplans/89318/check_uploading |
| http_status | 403 |
| body.code | ARG10002 |
| env | production, region us-west-2 |
| sqs_message_id | 6529504f-96d4-43c4-a332-5a0fac07aa02 (ApproximateReceiveCount: 1) |
| affected_model_id | floorplan 89318 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| sellen workspace (workspace.id 5266, facility 21088 — "8330 - PNB Seattle Sodo School") | 1 | 1건의 floorplan 업로드 처리 미완료. 사용자가 직접 trash 한 floorplan이므로 실사용자 영향 없음. |
Timeline#
- 2026-06-25 00:35 KST — Floorplan 89318 created in production (workspace 5266, facility 21088).
created_at: 2026-06-24T15:35:34.067Z - 2026-06-25 00:36 KST — agent가 SQS 메시지 수신,
BaseService::runByMessage | id: 89318로깅 (Datadog log @ 2026-06-24T15:36:27Z) - 2026-06-25 00:36 KST —
FloorplanService::translateFloorplan | source: /tmp/workspace/89318/source/36283.pdf, frame: 1, output: /tmp/workspace/89318/89318.png, width: 16384, height: 16384, dpi: 300, LOD: 6(Datadog log @ 2026-06-24T15:36:29Z) - 2026-06-25 00:36 KST — Floorplan 89318 trashed:
cycle_state: deleted,cycle_state_updated_at: 2026-06-24T15:36:38.000Z(Kibana floorplans index) - 2026-06-25 00:36 KST —
check_uploading호출 → 403 +Cupix::Errors::NotFound응답,BaseService::handlingMessageErrors에러 로깅 (Datadog log @ 2026-06-24T15:36:57Z). SQS 메시지는 동일 핸들러에서 삭제됨.
Error Log#
BaseService::handlingMessageErrors | Error and message object - {"error":{"statusCode":403,"requestUriHref":"http://api-tesla.cupix.internal/api/v1/floorplans/89318/check_uploading?fields%5B0%5D=id&fields%5B1%5D=name&fields%5B2%5D=state&fields%5B3%5D=resource_state&fields%5B4%5D=file_extension&fields%5B5%5D=page&fields%5B6%5D=resolution&fields%5B7%5D=cover_state&fields%5B8%5D=thumbnail_urls&fields%5B9%5D=cover_upload_url&fields%5B10%5D=floorplan_source&fields%5B11%5D=meta&fields%5B12%5D=creation_from&fields%5B13%5D=real_distance_in_millimeters&fields%5B14%5D=created_at&fields%5B15%5D=updated_at&fields%5B16%5D=upload_url&fields%5B17%5D=floorplan_type&fields%5B18%5D=published_at","bodyResult":{"code":"ARG10002","type":"Cupix::Errors::NotFound","reason":"Floorplan not found","message":"Floorplan not found"},"modelId":89318},"sqsMessage":{"MessageId":"6529504f-96d4-43c4-a332-5a0fac07aa02","Attributes":{"ApproximateReceiveCount":"1"}}}
Impact#
- Service:
cupixworks-any-floorplan-agent - Team: sellen
- 발생 횟수: 1
- 최초 발생: 2026-06-25 00:36 KST
- 최근 발생: 2026-06-25 00:36 KST
Root Cause Summary#
사용자가 floorplan 처리 중간에 floorplan 89318을 trash 한 결과, agent의 마지막 단계인 check_uploading API가 untrashed scope (Cyclable.visibility[:UNTRASHED]) 에서 floorplan을 찾지 못해 Cupix::Errors::NotFound (HTTP 403, ARG10002)를 반환했다. agent 측 BaseService::run 흐름은 전체 작업을 하나의 try/catch로 감싸기 때문에 translateFloorplan 과 uploadFile 이 성공한 뒤 check_uploading 만 실패해도 SQS 핸들러 전체가 에러로 떨어진다. agent의 getApiErrorToDeleteMessage 가 4xx 응답을 "delete-eligible" 로 분류해 SQS 메시지를 삭제하므로 재시도되지 않으며, 단일 사용자 행동(직접 삭제)에 의한 정상 race로 판단된다. 코드 결함보다는 "사용자 trash 가 처리 중에 발생한" 운영적 시나리오에 해당한다.
Technical Analysis#
Code Path#
- Entry point (agent):
packages/base/src/base-service.ts:108-110—await this.runByMessages()를 try/catch 로 감싸고, 어떤 단계든 throw 하면handlingMessageErrors(error)로 위임. - Step (agent):
packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:75-83— translate → upload →checkUploading순차 호출. - Failure point (agent):
packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:83에서 호출한this.cupixApi.floorplan.checkUploading(cpFloorplan.id)가 내부 HTTP 클라이언트 (CupixAuth::handleError) 에서 403 을 받아 reject. - Server failure point (tesla):
app/repositories/base_repository.rb:351-356— show 가 untrashed 모델을 못 찾고Cupix::Errors::NotFound.new(code: 'ARG10002', reason: "#{current_class.name} not found")를 raise. - Status mapping (tesla):
app/controllers/concerns/client_error_controller.rb:27—rescue_from Cupix::Errors::NotFound, with: :not_found_403_error로 HTTP 403 반환.
await this.updateFloorplanState(targetId, TESLA.FloorplanState.Processing);
await this.downloadFileWithHeader(
cpFloorplan.cpFloorplanSource.downloadUrl,
cpFloorplan.cpFloorplanSource.originalFilePath,
cpFloorplan.cpFloorplanSource.headers
);
await this.translateFloorplan(cpFloorplan);
await this.uploadFile(cpFloorplan.uploadUrl, cpFloorplan.localFilePath);
await this.cupixApi.floorplan.checkUploading(cpFloorplan.id);
try {
await this.runByMessages();
} catch (error) {
await this.handlingMessageErrors(error);
}
this.resetMessages();
await CPUtils.sleep(500);
await this.checkingQueue();
private getApiErrorToDeleteMessage = (error: any): any => {
// ... omitted: nil/system error guards
const statusCode = response.statusCode ? Number(response.statusCode) : undefined;
const requestUriHref = response.request?.uri?.href;
const bodyResult = response.body?.result;
const errorMsg = { statusCode, requestUriHref, bodyResult, modelId };
logger.warn('BaseService::getApiErrorToDeleteMessage | error msg - %s', JSON.stringify(errorMsg));
if (statusCode != undefined && statusCode >= 400 && statusCode <= 500) {
if (statusCode === 401) return; // 401 은 retry 대상
return errorMsg; // 그 외 4xx + 500 은 SQS 메시지 삭제 대상
}
return;
};
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
기대 동작: trash 된 floorplan 도 처리 중인 SQS 메시지 한 건은 정상 종료(또는 graceful skip)될 것.
실제 동작: tesla 의 set_floorplan before_action 이 trashed 모델을 NotFound 로 막아, 이미 끝나가던 처리 흐름이 마지막 줄에서 에러로 떨어진다. agent 의 createCPFloorplanByFloorplanId 는 동일한 ARG10002 코드를 무해(return undefined)로 처리하지만 (floorplan-service.ts:120-128), 처리 마지막 단계의 checkUploading 에는 동일한 가드가 없어 catch-all 핸들러로 던져진다.
Log Evidence#
Datadog query (재현):
service:cupixworks-any-floorplan-agent (89318)
처리 시작과 종료 로그(시간 순):
2026-06-24T15:36:27Z info BaseService::runByMessage | id: 89318
2026-06-24T15:36:29Z info FloorplanService::translateFloorplan | source: /tmp/workspace/89318/source/36283.pdf, frame: 1, output: /tmp/workspace/89318/89318.png, width: 16384, height: 16384, dpi: 300, LOD: 6
2026-06-24T15:36:57Z warn CupixAuth::handleError | Response statusCode: 403, requestUriHref: http://api-tesla.cupix.internal/api/v1/floorplans/89318/check_uploading?...
2026-06-24T15:36:57Z warn BaseService::getApiErrorToDeleteMessage | error msg - {"statusCode":403,...,"bodyResult":{"code":"ARG10002","type":"Cupix::Errors::NotFound","reason":"Floorplan not found","message":"Floorplan not found"},"modelId":89318}
2026-06-24T15:36:57Z info BaseService::cleanUpAnythingRelatedModel | path: /tmp/workspace/89318
2026-06-24T15:36:57Z error BaseService::handlingMessageErrors | Error and message object - { ...statusCode:403, modelId:89318, sqsMessageId:6529504f-..., ApproximateReceiveCount: "1" }
Kibana — floorplans index, id 89318 (production):
{
"id": 89318,
"state": "processing",
"name": "A2.2",
"floorplan_type": "pdf",
"page": 2,
"workspace": { "id": 5266, "name": "Active Projects " },
"facility": { "id": 21088, "name": "8330 - PNB Seattle Sodo School" },
"created_at": "2026-06-24T15:35:34.067Z",
"updated_at": "2026-06-24T15:35:34.939Z",
"tile_state": "created",
"cover_state": "created",
"cycle_state": "deleted",
"cycle_state_updated_at": "2026-06-24T15:36:38.000Z",
"availability_state": "available"
}
핵심 시간 차:
- 15:35:34Z floorplan 생성 → 15:36:27Z agent 처리 시작 → 15:36:38Z trash (cycle_state=deleted) → 15:36:57Z
check_uploading호출 시 NotFound. - agent 가 marble bucket 에 PDF 업로드와 PNG 변환을 마친 시점에 floorplan 은 이미 trashed 상태였다.
다른 동일 service 의 14:00–16:30Z 에러는 별개 ECONNRESET 케이스(floorplan 89294, 동일 클러스터 아님)로 fingerprint 가 다르며 본 인시던트와 무관하다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 처리 중 사용자가 floorplan 89318 을 trash 하여 check_uploading 이 untrashed scope 에서 못 찾음 |
Kibana: cycle_state=deleted, cycle_state_updated_at=2026-06-24T15:36:38Z (agent 처리 시작 11s 후, 에러 19s 전). BaseRepository.show 는 Cyclable.visibility[:UNTRASHED] 기본값 (base_repository.rb:121,355). 응답 코드 ARG10002 와 reason "Floorplan not found" 가 정확히 이 경로와 일치. |
— | Confirmed |
| H2 | 인증/세션 만료로 인한 403 | 응답이 401 이 아닌 403 + Cupix::Errors::NotFound. agent 의 401 분기(getApiErrorToDeleteMessage 에서 401 만 retry, base-service.ts:271) 가 발동되지 않음. body 에 명확히 code:"ARG10002", type:"Cupix::Errors::NotFound" 포함. |
401 이 아니라 403. 직전 동일 floorplan 의 translateFloorplan/uploadFile 가 성공. 인증이 살아 있었음을 의미. |
Rejected |
| H3 | api-tesla.cupix.internal 일시 장애 (외부 의존성) | — | incident-board.ts for-cluster 결과: active: null, recent: []. 같은 시간대 다른 floorplan 호출은 정상. body 가 정상 application JSON (code:"ARG10002")이라 백엔드가 정상 동작. |
Rejected |
| H4 | agent 가 잘못된 floorplan id 를 보냄 | — | SQS message body 의 id 와 모든 처리 단계 로그에서 일관되게 89318. 이전 단계(translateFloorplan)도 같은 id 로 성공. |
Rejected |
| H5 | tesla 측 set_floorplan/permission 회귀로 정상 floorplan 도 NotFound 반환 |
— | Pundit.policy(...).read? 실패는 PERM10000 으로 매핑(base_repository.rb:359-360)되며 본 케이스는 ARG10002. Kibana 데이터로 모델이 trashed 상태임을 직접 확인. |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
조치 불필요. 본 에러는 사용자 trash 와 agent 처리의 race 로 발생한 정상 시나리오 1회성 이벤트이며 데이터 손상이 없다. SQS 메시지는 getApiErrorToDeleteMessage 가 403 을 delete-eligible 로 처리해 이미 정상적으로 정리되었다.
단기 개선 (1주 이내)#
packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:83의checkUploading호출 실패 중body.result.code === 'ARG10002'인 경우는 warn 로그로 강등하는 것을 검토. 이미 동일 코드가createCPFloorplanByFloorplanId(floorplan-service.ts:120-128) 에서는 무해하게 처리되지만, 처리 마지막 단계에서는 catch-all 로 떨어져error레벨이 된다. 사용자 trash 에 의한 정상 race 는 운영자 알람을 발생시키지 않는 것이 바람직하다.- 강등 대신 또는 추가로,
runByMessages의 마지막 단계에서cupFloorplan이 여전히 활성인지 가벼운 GET 으로 사전 확인 후checkUploading을 건너뛰는 가드도 옵션. 단 race window 자체는 닫지 못함을 인지하고 trade-off 평가 필요.
장기 개선 (재발 방지)#
- tesla 쪽에서 agent 전용 internal endpoint 가 trashed 모델에도 idempotent 하게 응답하도록 별도 scope (예:
Cyclable.visibility[:ALL_INCLUDING_TRASHED]+cycle_state필드 반환)를 노출하면, agent 가 사용자 trash 와 시스템 에러를 구분해 처리할 수 있다. 같은 패턴이 다른 *-agent 서비스에도 적용된다. - 재발 빈도 모니터링이 1주 이상 지속적으로 0 에 가깝다면 어떤 변경도 하지 않아도 된다(현재 occurrence_count: 1).
Monitoring#
agent 측에서 사용자 trash 로 인한 NotFound race 가 늘어나는지 추적용:
service:cupixworks-any-floorplan-agent status:error "BaseService::handlingMessageErrors" "ARG10002" "Floorplan not found"
전체 floorplan-agent 에러 base rate 비교 (회귀 감지):
service:cupixworks-any-floorplan-agent status:error "BaseService::handlingMessageErrors"
check_uploading 만 별도로 추적:
service:cupixworks-any-floorplan-agent "check_uploading" "statusCode\":403"
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial (코드 변경 권장하지 않음; 운영적 race 시나리오로 분류)