ES /docs

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#

  1. 2026-08-06 23:46:18 KSTBaseService::runByMessage | id: 92247 — agent 가 floorplan 92247 메시지 수신
  2. 2026-08-06 23:46:18 KSTCupixAuth::handleError | Response statusCode: 403 ... GET /api/v1/floorplans/92247 ... body.result: {"code":"ENT4000", ... "reason":"Floorplan not found"} — tesla 가 403 반환
  3. 2026-08-06 23:46:18 KSTBaseService::getApiErrorToDeleteMessage | error msg - {"statusCode":403, ..., "modelId":92247} — 4xx 로 판정 → 삭제 대상
  4. 2026-08-06 23:46:18.433 KSTAwsQueueManager::deleteMessage | end - message id: 9f075323... — 메시지 삭제 완료
  5. 2026-08-06 23:46:18.435 KSTBaseService::handlingMessageErrors | sqsMessage - {...}, error:error 레벨 로깅 (cluster first/last_seen)

Error Log#

Datadog Logs

text
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 케이스인 ENT4000throw ec 로 재전파한다. 이 예외가 BaseService.runByMessage → runByMessages 를 거쳐 handlingMessageErrors 에 도달해 error 레벨로 로깅되면서 Error Tracking noise 가 된다. 두 코드 모두 동일한 Cupix::Errors::NotFound(HTTP 403) 이고 사용자·재시도 영향이 없으므로 실제 결함은 아니며, agent 가 ENT4000ARG10002 와 동일하게 취급하지 않는 처리 불일치가 유일한 문제다.

Technical Analysis#

Code Path#

  • Entry point: packages/base/src/base-service.ts:153 (runByMessage) → :170 this.run(targetId, msgObject)
  • floorplan agent 진입: packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:51 (run) → :53 createCPFloorplanByFloorplanId(targetId)
  • API 호출 및 실패 지점: floorplan-service.ts:115 this.cupixApi.floorplan.get(floorplanId) → catch :120
  • Failure point: floorplan-service.ts:128 throw ec (ENT4000 미흡수)
  • 상위 로깅: base-service.ts:109-111 catch → handlingMessageErrors:318-319 error 로그

tesla 측에서 ENT4000 을 raise 하는 지점 — 레코드가 trash 에 있을 때만 ENT4000, 아예 없으면 ARG10002:

app/repositories/base_repository.rb:351-357ruby
    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 으로 매핑된다:

app/controllers/concerns/client_error_controller.rb:27,57-58ruby
    rescue_from Cupix::Errors::NotFound, with: :not_found_403_error
    # ...
    def not_found_403_error(exception)
      raise_error(403, exception)

agent 의 catch 절이 ARG10002 만 흡수하고 ENT4000 은 재전파:

packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:120-129typescript
			.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 레벨 로그:

packages/base/src/base-service.ts:290-313 (origin/develop 배포 형태)typescript
	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 만 예외):

packages/base/src/base-service.ts:270-275typescript
		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 하여 본문이 그대로 남는다.

사용한 쿼리:

text
service:cupixworks-any-floorplan-agent "9f075323-20ba-4345-b43f-dec825086dd8"

floorplan 92247 처리 직전~직후의 실제 로그(같은 session, 2026-08-06 23:46:18 KST):

text
service:cupixworks-any-floorplan-agent 92247   (2026-08-06T14:40:00Z ~ 14:47:00Z)
json
{ "status": "info", "message": "BaseService::runByMessage | id: 92247" }
json
{
  "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\"}"
}
json
{
  "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}"
}
json
{ "status": "info", "message": "AwsQueueManager::deleteMessage | end - message id: 9f075323-20ba-4345-b43f-dec825086dd8" }

이전 배포 형태에서는 동일 root cause 가 error 본문까지 그대로 남는다(2026-07-24 등):

text
service:cupixworks-any-floorplan-agent ("Floorplan not found" OR "handlingMessageErrors")
text
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"), 일자별):

text
  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 용):

text
service:cupixworks-any-floorplan-agent "ENT4000" "Floorplan not found"

전체 error-handler 진입 추이(포맷 무관):

text
service:cupixworks-any-floorplan-agent status:error "handlingMessageErrors"

단기 개선(ENT4000 흡수) 배포 후 재발 확인 — 값이 0 으로 수렴해야 함:

text
service:cupixworks-any-floorplan-agent status:error "handlingMessageErrors" "ENT4000"

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial (catch 절 조건 한 줄 추가)