ES /docs

BaseService::handlingMessageErrors | Error and message object - {"error":"undefined response","sqsMe

RCA: BaseService::handlingMessageErrors — "undefined response" (Lens covered video)

Overview#

What Happened#

cupixworks-capture-preprocessor-agent 가 SQS 메시지를 처리하던 중 PreprocessorService.checkSupportedVideo 의 도메인 검증에서 "Lens covered video detected." Error 가 발생했고, BaseService::handlingMessageErrors 가 이 에러의 원인을 식별하지 못해 error: "undefined response" 형태의 에러 로그를 남겼다. 2026-06-25 05:30 KST 와 06:19 KST 두 차례 발생했으며, 클러스터 fingerprint 기준 24시간 내 같은 패턴(빈 error.response)으로만 따지면 3회까지 관측된다.

Quick Facts#

Field Value
exception.class Error (plain new Error(reason))
exception.message Lens covered video detected.
top_frame packages/cupix-capture-preprocessor-agent/src/preprocessor-service.ts:865
logged as BaseService::handlingMessageErrors | Error and message object - {"error":"undefined response", ...}
env production, us-west-2

Affected Teams#

Team / Domain Error Count Impact
mortenson / capture preprocessing 2 "lens covered" 검증에 걸린 capture 의 실패 원인이 로그에서 "undefined response" 로만 보이므로, 운영자가 외부 의존성 문제로 오인할 수 있음. 실제 capture 처리 자체는 정상적으로 실패 처리된다.

Timeline#

  1. 2026-06-25 05:30:20 KSTCupixAuth::handleError | Undefined response: ... "Lens covered video detected." ... warn 로그 (sqsMessage b8c093e5-b871-408a-a749-54cd0612bc47)
  2. 2026-06-25 05:30:21 KSTBaseService::handlingMessageErrors | Error and message object - {"error":"undefined response", ...} error 로그 (cluster first_seen)
  3. 2026-06-25 06:19:08 KST — 동일 패턴 warn 로그 (sqsMessage 31e700b7-08e1-4533-b7cb-945fd37ae748)
  4. 2026-06-25 06:19:09 KST — 동일 패턴 error 로그 (cluster last_seen)
  5. 2026-06-25 06:27:17–18 KST — 동일 패턴 한 차례 더 (sqsMessage fabaeded-ba68-45cc-a2cc-0ccd47a129de); cluster 집계는 2건이지만 패턴은 지속됨

Error Log#

Datadog Logs

text
BaseService::handlingMessageErrors | Error and message object - {"error":"undefined response","sqsMessage":{"MessageId":"b8c093e5-b871-408a-a749-54cd0612bc47","Attributes":{"ApproximateReceiveCount":"1"}}}

Impact#

  • Service: cupixworks-capture-preprocessor-agent
  • Team: mortenson
  • 발생 횟수: 2
  • 최초 발생: 2026-06-25 05:30 KST
  • 최근 발생: 2026-06-25 06:19 KST

운영 영향은 낮다. SQS 메시지는 getApiErrorToDeleteMessage'undefined response' (truthy 문자열)을 반환하므로 정상적으로 삭제되고, capture 의 error state 도 갱신된다. 그러나 에러 로그가 도메인 실패(렌즈가 가려진 비디오)를 외부 응답 누락처럼 표현해 운영자/온콜이 잘못된 트리아지를 할 위험이 있다.

Root Cause Summary#

PreprocessorService.checkSupportedVideo 가 비디오 검증 실패 시 HTTP response 가 아닌 plain new Error(result.reason) 을 throw 하는데, BaseService.getApiErrorToDeleteMessageCupixAuth.handleError 모두 error.response 가 있는 HTTP 에러를 가정하고 작성되었다. 두 함수 모두 response == undefined 분기에서 'undefined response' 라는 문자열만 남기기 때문에, 실제 원인인 Lens covered video detected. 메시지가 BaseService 의 최종 error 로그에서 사라진다. 즉, 도메인 검증 실패와 HTTP 응답 누락이 같은 코드 경로로 합쳐져 있는 분류 누락이 root cause 다.

Technical Analysis#

Code Path#

  • Entry point: packages/base/src/base-service.ts:108runByMessages() try/catch
  • Failure throw: packages/cupix-capture-preprocessor-agent/src/preprocessor-service.ts:865throw new Error(result.reason)
  • API-layer warn: packages/api/src/authentication/cupix-auth.ts:54CupixAuth::handleError | Undefined response
  • Final error log: packages/base/src/base-service.ts:311BaseService::handlingMessageErrors | Error and message object
  • Misclassification: packages/base/src/base-service.ts:249-253error.response 미존재 시 'undefined response' 문자열 반환

검증 실패는 자식 프로세스가 쓴 valid.jsonstatus_code 가 0 보다 클 때 발생한다:

packages/cupix-capture-preprocessor-agent/src/preprocessor-service.ts:860-866typescript
const result = CPUtils.isJsonString(_data) ? JSON.parse(_data) : undefined;
if (result.status_code > 0) {
    const _errorCode = ErrorCode.Scenemapper.statusCode(result.status_code.toString());
    this.jobManager.setErrorCode(_errorCode);
    logger.error('PreprocessorService::checkSupportedVideo | end - found unsupported video - %s', _errorCode);
    throw new Error(result.reason);
}

여기서 throw 되는 객체는 plain Error 이므로 .response, .errno, .code, .syscall 모두 없다. 호출 경로 어디선가 CupixAuth.handleError 를 통과하면 다음 분기를 탄다:

packages/api/src/authentication/cupix-auth.ts:42-56typescript
handleError = (ec: any): any => {
    const response = ec && CPUtils.isJsonString(ec) ? JSON.parse(ec) : ec.response;
    if (response != undefined) {
        // ... HTTP 분기 (statusCode/uri/body)
    } else {
        logger.warn('CupixAuth::handleError | Undefined response: %s', JSON.stringify(ec, Object.getOwnPropertyNames(ec)));
    }
    return ec;
};

CupixAuth::handleError | Undefined response: {"stack":"...","message":"Lens covered video detected."} warn 로그가 남는다. 이 부분이 Datadog 로그에서 동일 시각에 페어로 관측된다.

마지막으로 BaseService 에서:

packages/base/src/base-service.ts:240-253typescript
private getApiErrorToDeleteMessage = (error: any): any => {
    if (error == undefined) { /* ... */ return 'undefined error'; }
    if (error.errno != undefined && error.code != undefined && error.syscall != undefined) { /* ... */ return; }
    const response = CPUtils.isJsonString(error) ? JSON.parse(error) : error.response;
    if (response == undefined) {
        logger.warn('BaseService::getApiErrorToDeleteMessage | undefined response', error);
        return 'undefined response';
    }
    // ...
};

error.response 는 undefined 이므로 'undefined response' 문자열을 반환. 이 값이 그대로 errorAndMessage.error 에 들어가 최종 로그에 노출된다:

packages/base/src/base-service.ts:290-311typescript
private handlingMessageErrors = async (error: any): Promise<void> => {
    const errorAndMessage = { error: error, sqsMessage: {} };
    if (this.messageInProcess) {
        errorAndMessage.sqsMessage = { MessageId: ..., Attributes: ... };
        const apiErrorObject = this.getApiErrorToDeleteMessage(error);
        if (apiErrorObject != undefined || this.checkReceiveCountToDeleteMessage()) {
            try {
                errorAndMessage.error = apiErrorObject;          // <-- 'undefined response' 문자열로 덮어씀
                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();
};

기대 동작: 도메인 검증 실패 시 result.reason ("Lens covered video detected.") 와 errorCode 가 로그에 남아야 한다. 실제 동작: 원본 메시지가 'undefined response' 문자열로 치환되어 도메인 정보가 사라진다.

Log Evidence#

Datadog 쿼리:

text
service:cupixworks-capture-preprocessor-agent "BaseService::handlingMessageErrors"
text
service:cupixworks-capture-preprocessor-agent "undefined response"

페어로 관측된 warn + error 로그 (2026-06-25 05:30 KST):

json
{
  "timestamp": "2026-06-25 05:30:20",
  "status": "warn",
  "message": "CupixAuth::handleError | Undefined response: {\"stack\":\"Error: Lens covered video detected.\n    at PreprocessorService.checkSupportedVideo (/tmp/agent/dist/app.cjs:9899:15)\n    ...\n    at async BaseService2.runByMessage (/tmp/agent/dist/app.cjs:6151:15)\n    at async BaseService2.runByMessages (/tmp/agent/dist/app.cjs:6139:13)\n    at async BaseService2.checkingQueue (/tmp/agent/dist/app.cjs:6103:15)\n    ...\",\"message\":\"Lens covered video detected.\"}"
}
json
{
  "timestamp": "2026-06-25 05:30:21",
  "status": "error",
  "message": "BaseService::handlingMessageErrors | Error and message object - {\"error\":\"undefined response\",\"sqsMessage\":{\"MessageId\":\"b8c093e5-b871-408a-a749-54cd0612bc47\",\"Attributes\":{\"ApproximateReceiveCount\":\"1\"}}}"
}

24시간 내 동일 서비스에서 "undefined response" 키워드는 12건 관측되었고, 이 중 BaseService::handlingMessageErrors"error":"undefined response" 는 3건 (이번 cluster 의 2건 + 06:27:18 KST 1건). 나머지 9건은 CupixAuth::handleError | Undefined response: ... warn 로그로, "Lens covered video detected." (3건), ETIMEDOUT (2건), ECONNRESET (4건) 메시지를 포함한다. 즉 같은 코드 경로가 도메인 에러와 네트워크 에러를 동일하게 처리한다.

비교를 위해, 같은 BaseService 로그 11건 중 9건은 error.response 가 채워진 정상 분기 — statusCode:403 + bodyResult:{"code":"ENT4000","reason":"Capture not found"} 형태로 풍부한 컨텍스트를 남긴다 (2026-06-24 19:47, 19:16, 18:57, 18:46, 16:00 등 다수). 즉 HTTP 분기는 잘 동작하고, plain Error 분기만 정보가 유실된다.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 checkSupportedVideothrow new Error(result.reason)getApiErrorToDeleteMessage / CupixAuth.handleError 의 HTTP-response 가정과 어긋나 도메인 컨텍스트가 'undefined response' 문자열로 평탄화된다 preprocessor-service.ts:865 의 plain Error throw, base-service.ts:249-253 의 fallback, 같은 시각 페어로 찍힌 warn(Lens covered video detected.) + error("error":"undefined response") 로그 Confirmed
H2 외부 API (api-tesla.cupix.internal) 가 빈 응답을 돌려주어 발생 같은 서비스에서 ETIMEDOUT/ECONNRESET 워닝도 보임 (22:08–22:48 KST) 이번 cluster 의 두 건은 stack trace 가 정확히 PreprocessorService.checkSupportedVideo 에서 시작하며 HTTP request 호출 흔적이 없음. CupixAuth::handleError 에 들어온 객체에도 statusCode/uri 가 없고 message 만 Lens covered video detected. 로 찍힘 Rejected
H3 valid.json 파싱 실패로 result 가 undefined → result.status_code 접근 시 TypeError 가 throw 됨 preprocessor-service.ts:860 의 JSON.parse 실패 가능성 TypeError 라면 stack 첫 프레임이 JSON.parse 또는 checkSupportedVideo 의 line 861 이어야 하는데, 로그 stack 은 line 9899 (checkSupportedVideo) 의 throw new Error(result.reason) 와 일치하고 message 가 정확히 Lens covered video detected. 로 도메인 reason 문자열 Rejected
H4 SQS poison message 로 메시지가 무한 retry 됨 ApproximateReceiveCount:"1" 이라 retry 이력 없음. getApiErrorToDeleteMessage 가 truthy('undefined response')를 반환하므로 deleteByMessage 가 호출되어 즉시 삭제 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

없음. 사용자 영향이 없는 로깅 분류 문제이며, 메시지/state 처리는 정상.

단기 개선 (1주 이내)#

  • packages/base/src/base-service.ts:240-253 getApiErrorToDeleteMessageerror instanceof Error 인 경우 (HTTP response 도 아니고 nodejs syscall 에러도 아닌 경우)에 대해 별도 분기를 두어 error.message / error.name / 가능하면 jobManager.errorCode 를 반환하도록 한다. 그래야 errorAndMessage.errorLens covered video detected. 가 그대로 남는다.
  • packages/api/src/authentication/cupix-auth.ts:42-56 CupixAuth.handleError 의 fallback 분기는 도메인 에러까지 잡고 있다. 호출처(예: transfer.manager.ts:73,78,83,102)에서 정말 HTTP response 인 경우에만 handleError 를 호출하도록 정리하거나, fallback 로그 레벨을 케이스별로 조정한다. 현재 동일 plain Error 가 두 군데에서 "Undefined response" 로 두 번 로깅된다.
  • cupix-capture-preprocessor-agent 의 도메인 검증 실패는 error 가 아닌 warn (또는 별도 도메인 에러 클래스) 로 분류해 운영 알림 노이즈를 줄인다. 메모리 노트(AUTH20022/AUTH20023 episode) 와 같은 맥락 — "expected operational scenario" 는 error 가 아니어야 한다.

장기 개선 (재발 방지)#

  • agents 공통 base 에 도메인 에러 / HTTP 에러 / 시스템 에러를 구분하는 타입 계층을 도입 (DomainValidationError, UpstreamHttpError, SystemError). 현재 any 로 받는 패턴이 분류 누락을 만든다.
  • valid.jsonstatus_code 매핑을 ErrorCode.Scenemapper 처럼 코드/메시지 모두 갖춘 enum 으로 throw 해, 로그에서 errorCode 가 자동으로 노출되도록 한다.

Monitoring#

error: "undefined response" 패턴 자체가 분류 누락의 카나리이므로 agents 전체에서 추적한다.

text
service:cupixworks-capture-preprocessor-agent status:error "BaseService::handlingMessageErrors" "undefined response"
text
service:(cupixworks-capture-preprocessor-agent OR cupixworks-capture-postprocessor-agent OR cupixworks-tesla-forge-agent) "CupixAuth::handleError" "Undefined response"
text
service:cupixworks-capture-preprocessor-agent "Lens covered video detected"

세 쿼리는 Datadog timeseries widget 에서 그대로 그래프로 렌더 가능하다 (monitor-only 문법 사용 안 함). 첫 쿼리가 0 으로 떨어지면 분류 패치가 반영된 것이다.

Risk Assessment#

  • Risk level: low — 사용자 영향 없음, 메시지 처리 정상, 로그 가독성/트리아지 정확도 문제만 존재
  • 예상 복잡도: standard — base-service / cupix-auth 두 곳의 분기를 손봐야 하고, agents 다수 패키지에서 동일 패턴이 사용 중이라 회귀 테스트 필요