BaseService::handlingMessageErrors | sqsMessage - {"MessageId":"9f075323-20ba-4345-b43f-dec825086dd8
RCA: BaseService::handlingMessageErrors | sqsMessage 403 ENT4000 "Floorplan not found"
Overview#
What Happened#
cupixworks-any-floorplan-agent 가 SQS 메시지로 floorplan 92247 처리를 시작한 직후, tesla API 의 GET /api/v1/floorplans/92247 가 HTTP 403 (ENT4000 Cupix::Errors::NotFound "Floorplan not found") 를 반환했다. 이 floorplan 은 큐잉 이후 처리 직전에 trash(soft-delete) 상태로 넘어간 레코드다. agent 의 예외 핸들러(createCPFloorplanByFloorplanId)는 "없는 floorplan" 케이스를 정상적으로 건너뛰도록 만들어져 있으나, ARG10002 코드만 흡수하고 trash 케이스인 ENT4000 는 흡수하지 못해 예외가 상위 BaseService.handlingMessageErrors 까지 전파되어 error 레벨로 로깅되었다. 메시지는 4xx 로 판정되어 정상 삭제되었고 재시도·사용자 영향은 없다 — noise 다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Cupix::Errors::NotFound (agent 측에서는 rethrow 된 API error object) |
| exception.message | {"statusCode":403,"bodyResult":{"code":"ENT4000","type":"Cupix::Errors::NotFound","reason":"Floorplan not found"}} |
| top_frame | packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:128 (throw ec) |
| runtime | Node.js agent (cupixworks applications/agents) |
| env | production, us-west-2 |
| host | ip-10-1-168-13.us-west-2.compute.internal |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| worley (rep) + 기타 tenant | 14d 창 89 "Floorplan not found" 로그 (54 ENT4000) | 없음 — floorplan 은 이미 trash, 메시지 삭제됨. 로그 noise 만 발생 |
Timeline#
- 2026-08-06 23:46:18 KST —
BaseService::runByMessage | id: 92247— agent 가 floorplan 92247 메시지 수신 - 2026-08-06 23:46:18 KST —
CupixAuth::handleError | Response statusCode: 403 ... GET /api/v1/floorplans/92247 ... body.result: {"code":"ENT4000", ... "reason":"Floorplan not found"}— tesla 가 403 반환 - 2026-08-06 23:46:18 KST —
BaseService::getApiErrorToDeleteMessage | error msg - {"statusCode":403, ..., "modelId":92247}— 4xx 로 판정 → 삭제 대상 - 2026-08-06 23:46:18.433 KST —
AwsQueueManager::deleteMessage | end - message id: 9f075323...— 메시지 삭제 완료 - 2026-08-06 23:46:18.435 KST —
BaseService::handlingMessageErrors | sqsMessage - {...}, error:—error레벨 로깅 (cluster first/last_seen)
Error Log#
BaseService::handlingMessageErrors | sqsMessage - {"MessageId":"9f075323-20ba-4345-b43f-dec825086dd8","Attributes":{"ApproximateReceiveCount":"1"}}, error:
Impact#
- Service:
cupixworks-any-floorplan-agent - Team: worley
- 발생 횟수: 1 (cluster) — 14d 창 실측 89 "Floorplan not found" 관련 로그
- 최초 발생: 2026-08-06 23:46 KST
- 최근 발생: 2026-08-06 23:46 KST
Root Cause Summary#
floorplan 이 SQS 로 큐잉된 뒤 agent 가 처리하기 직전에 trash(soft-delete) 되면, tesla BaseRepository#show 는 활성 scope 에서 레코드를 못 찾고 trash 에는 존재하므로 ENT4000 Cupix::Errors::NotFound "Floorplan not found" 를 raise 하여 HTTP 403 을 반환한다(base_repository.rb:351-356, client_error_controller.rb:27,57-58). agent 의 createCPFloorplanByFloorplanId catch 절(floorplan-service.ts:120-129)은 "존재하지 않는 floorplan" 을 정상 skip 하도록 설계되었으나 오직 ARG10002 코드만 undefined 반환으로 흡수하고, trash 케이스인 ENT4000 은 throw ec 로 재전파한다. 이 예외가 BaseService.runByMessage → runByMessages 를 거쳐 handlingMessageErrors 에 도달해 error 레벨로 로깅되면서 Error Tracking noise 가 된다. 두 코드 모두 동일한 Cupix::Errors::NotFound(HTTP 403) 이고 사용자·재시도 영향이 없으므로 실제 결함은 아니며, agent 가 ENT4000 을 ARG10002 와 동일하게 취급하지 않는 처리 불일치가 유일한 문제다.
Technical Analysis#
Code Path#
- Entry point:
packages/base/src/base-service.ts:153(runByMessage) →:170this.run(targetId, msgObject) - floorplan agent 진입:
packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:51(run) →:53createCPFloorplanByFloorplanId(targetId) - API 호출 및 실패 지점:
floorplan-service.ts:115this.cupixApi.floorplan.get(floorplanId)→ catch:120 - Failure point:
floorplan-service.ts:128throw ec(ENT4000 미흡수) - 상위 로깅:
base-service.ts:109-111catch →handlingMessageErrors→:318-319error로그
tesla 측에서 ENT4000 을 raise 하는 지점 — 레코드가 trash 에 있을 때만 ENT4000, 아예 없으면 ARG10002:
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::NotFound 은 controller 에서 403 으로 매핑된다:
rescue_from Cupix::Errors::NotFound, with: :not_found_403_error
# ...
def not_found_403_error(exception)
raise_error(403, exception)
agent 의 catch 절이 ARG10002 만 흡수하고 ENT4000 은 재전파:
.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('FloorplanService::createCPFloorplanByFloorplanId | end - reason: %s', reason);
if (code === 'ARG10002') return undefined;
}
throw ec;
});
기대 동작: floorplan 이 없거나 trash 이면 처리를 조용히 skip(정상 종료). 실제 동작: ARG10002(완전 삭제)만 skip 되고, ENT4000(trash)은 throw 되어 상위에서 error 로깅.
전파 후 상위 핸들러 — 4xx 이므로 메시지 삭제 후 error 레벨 로그:
private handlingMessageErrors = async (error: any): Promise<void> => {
// ... getApiErrorToDeleteMessage(error) 가 statusCode 403 (400~500, !=401) 이므로 삭제 대상 판정
// deleteByMessage(this.messageInProcess) 로 메시지 삭제
logger.error('BaseService::handlingMessageErrors | sqsMessage - %s, error:',
JSON.stringify(errorAndMessage.sqsMessage), errorAndMessage.error);
};
메시지 삭제 판정 로직(4xx 는 삭제, 401 만 예외):
if (statusCode != undefined && statusCode >= 400 && statusCode <= 500) {
if (statusCode === 401) return;
return errorMsg;
}
return;
Log Evidence#
representative sample 의 error: 뒤가 비어 보이는 이유: TSLA-13277 이후 배포 형태(sqsMessage - %s, error:)는 error 를 splat 인자로 넘기므로 Datadog message 필드에 error 본문이 인라인되지 않는다. 이전 배포 형태(Error and message object - %s)는 전체를 JSON.stringify 하여 본문이 그대로 남는다.
사용한 쿼리:
service:cupixworks-any-floorplan-agent "9f075323-20ba-4345-b43f-dec825086dd8"
floorplan 92247 처리 직전~직후의 실제 로그(같은 session, 2026-08-06 23:46:18 KST):
service:cupixworks-any-floorplan-agent 92247 (2026-08-06T14:40:00Z ~ 14:47:00Z)
{ "status": "info", "message": "BaseService::runByMessage | id: 92247" }
{
"status": "warn",
"message": "CupixAuth::handleError | Response statusCode: 403, requestUriHref: http://api-tesla.cupix.internal/api/v1/floorplans/92247?fields[0]=id&..., body.result: {\"code\":\"ENT4000\",\"type\":\"Cupix::Errors::NotFound\",\"reason\":\"Floorplan not found\",\"message\":\"Floorplan not found\"}"
}
{
"status": "warn",
"message": "BaseService::getApiErrorToDeleteMessage | error msg - {\"statusCode\":403,\"requestUriHref\":\"http://api-tesla.cupix.internal/api/v1/floorplans/92247?...\",\"bodyResult\":{\"code\":\"ENT4000\",\"type\":\"Cupix::Errors::NotFound\",\"reason\":\"Floorplan not found\",\"message\":\"Floorplan not found\"},\"modelId\":92247}"
}
{ "status": "info", "message": "AwsQueueManager::deleteMessage | end - message id: 9f075323-20ba-4345-b43f-dec825086dd8" }
이전 배포 형태에서는 동일 root cause 가 error 본문까지 그대로 남는다(2026-07-24 등):
service:cupixworks-any-floorplan-agent ("Floorplan not found" OR "handlingMessageErrors")
BaseService::handlingMessageErrors | Error and message object - {"error":{"statusCode":403,...,"bodyResult":{"code":"ENT4000","type":"Cupix::Errors::NotFound","reason":"Floorplan not found","message":"Floorplan not found"},"modelId":12786},"sqsMessage":{"MessageId":"96bf6e37-...","Attributes":{"ApproximateReceiveCount":"1"}}}
빈도(14d, ("Floorplan not found" OR "handlingMessageErrors"), 일자별):
52 2026-07-24 (연속 ID 12786~12795 등 — 대량 trash 배치)
1 2026-07-27
1 2026-07-28
6 2026-07-30
5 2026-07-31
21 2026-08-04
1 2026-08-05
5 2026-08-06
distinct floorplan ID 가 광범위(12786~12795, 91932, 25873, 26217, 26218, 92247...)하고 모두 ApproximateReceiveCount:1(첫 수신에 즉시 4xx 삭제) → 특정 레코드 재진입이 아니라 "trash 된 floorplan 이 큐에 남아있다 처리되는" 광범위 저빈도 패턴. status-board scope svc:cupixworks-any-floorplan-agent::unknown, active 인시던트 없음.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | floorplan 이 큐잉 후 처리 직전 trash → tesla 403 ENT4000 → agent 가 ARG10002 만 흡수하고 ENT4000 은 rethrow → error 로깅 (noise) | 로그 CupixAuth::handleError statusCode:403 body.result code:ENT4000 "Floorplan not found" (92247); base_repository.rb:352-353 trash 시 ENT4000; floorplan-service.ts:126 if (code === 'ARG10002') return undefined 만; client_error_controller.rb:27,57 NotFound→403 |
없음 — 메시지 삭제·재시도 없음·사용자 영향 없음 | Confirmed |
| H2 | 인증/권한 실패로 인한 403 (토큰 만료) | 403 응답 존재 | body.result 가 PERM10000/401 이 아니라 ENT4000/Cupix::Errors::NotFound; getApiErrorToDeleteMessage 는 401 을 별도 skip 하는데 여기선 삭제됨 |
Rejected |
| H3 | TSLA-13277 로그 포맷 변경이 새로운 에러를 유발 | 새 포맷 sqsMessage - %s, error: 이 최근에 등장 |
동일 root cause(ENT4000)가 구 포맷 로그(2026-07-24)에도 존재; 변경은 로깅 방식일 뿐(f9860dc41 diff 는 logger 인자 형태만 수정) → 발생 원인 아님, 단지 representative message 가 truncate 되어 보이는 이유 |
Rejected |
| H4 | agent 코드가 floorplan 을 잘못된 ID 로 조회 | — | msgObject.id(92247) 그대로 조회, tesla 가 "trash 에 존재" 로 응답 → ID 는 유효, 상태만 trash |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
없음. 실제 처리 실패·데이터 손상·사용자 영향이 없고, 4xx 로 메시지가 정상 삭제되므로 긴급 코드 수정이 필요하지 않다. Error Tracking 에서 이 cluster 는 IGNORE 권장.
단기 개선 (1주 이내)#
packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:120-129의 catch 절에서 "처리 대상이 이미 없어진(trash 포함) floorplan" 을 정상 skip 으로 취급하도록, 흡수 대상 코드에ENT4000을 추가한다. 근거:base_repository.rb:351-356에서ARG10002(완전 부재)와ENT4000(trash)는 동일Cupix::Errors::NotFound이며 agent 관점에서 "더 이상 처리할 floorplan 이 없음" 이라는 의미가 같다. 이렇게 하면 예외가 상위로 전파되지 않아error로깅 자체가 사라진다.- 대안(로그 레벨 조정): 전파를 유지하되
handlingMessageErrors에서 4xx(특히 NotFound 계열) 삭제는error가 아니라warn으로 기록. 단, 이는 base 공통 경로라 다른 agent 에도 영향을 주므로 floorplan-agent 국소 흡수(위 방식)를 우선한다.
장기 개선 (재발 방지)#
- floorplan 을 trash 할 때 관련 SQS 처리 메시지를 무효화/드레인하거나, agent 가 처리 시작 시
state/resource_state를 먼저 확인해 trashed 면 조기 종료하도록 producer↔consumer 계약을 정리(큐잉 후 상태 변경 레이스 최소화). - agent 공통 base 에서 "대상 리소스 부재(NotFound 계열)" 를 처리 실패와 구분하는 표준 분류를 도입(각 agent 가 코드 문자열을 개별 하드코딩하지 않도록).
Monitoring#
trash 로 인한 floorplan NotFound 발생 추이(대시보드 timeseries widget 용):
service:cupixworks-any-floorplan-agent "ENT4000" "Floorplan not found"
전체 error-handler 진입 추이(포맷 무관):
service:cupixworks-any-floorplan-agent status:error "handlingMessageErrors"
단기 개선(ENT4000 흡수) 배포 후 재발 확인 — 값이 0 으로 수렴해야 함:
service:cupixworks-any-floorplan-agent status:error "handlingMessageErrors" "ENT4000"
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial (catch 절 조건 한 줄 추가)