ES /docs

CupixAuth#handleError missing .response null check — undefined propagation

RCA: FloorplanService::uploadFile 500 Internal Server Error

Overview#

What Happened#

2026-06-16 20:18:45 KST에 us-west-2 리전의 cupixworks-any-floorplan-agent (cupix-tesla-floorplan-agent) 가 floorplan id 88239 의 변환된 PNG 파일을 presigned uploadUrl 로 PUT 업로드하던 중 HTTP 500 Internal Server Error 응답을 받았다. 업로드 실패 후 에러 핸들러 체인에서 부수적으로 Cannot read properties of undefined (reading 'response') 가 발생하여 SQS 메시지가 정상적으로 처리되지 못했다. 동일 source PDF (id 35955) 로 작업하던 인접 floorplan 88240 은 같은 시각에 정상 성공했다.

Quick Facts#

Field Value
exception.class (no thrown class — non-200 HTTP response from upload endpoint)
exception.message FloorplanService::uploadFile | response path: /tmp/workspace/88239/88239.png, code: 500, message: Internal Server Error
top_frame packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:295-300
runtime Node.js / TypeScript agent (cupixworks/applications/agents)
env production, us-west-2
tenant cupix

Affected Teams#

Team / Domain Error Count Impact
nestle (cupix-tesla-floorplan-agent) 1 Floorplan id 88239 의 tile 업로드 단계로 진입하지 못함. SQS 메시지 처리 실패 (재시도 가능)

Timeline#

  1. 2026-06-16 20:18:19 KSTBaseService::runByMessage | id: 88239 — SQS 메시지 수신, 처리 시작
  2. 2026-06-16 20:18:22 KSTFloorplanService::translateFloorplan 시작 (source: 35955.pdf, 16384x16384, LOD 6)
  3. 2026-06-16 20:18:45 KSTFloorplanService::uploadFile PUT 요청에 대한 HTTP 500 응답 수신
  4. 2026-06-16 20:18:45 KSTCupixAuth::handleError | Undefined response: {"statusCode":500,"request":{"method":"PUT"}} (warn)
  5. 2026-06-16 20:18:45 KSTCannot read properties of undefined (reading 'response') 에러 (error)
  6. 2026-06-16 20:18:45 KSTBaseService::getApiErrorToDeleteMessage | undefined error (warn) → BaseService::handlingMessageErrors | Error and message object - {"error":"undefined error","sqsMessage":{...,"ApproximateReceiveCount":"1"}}

Error Log#

Datadog Logs

text
FloorplanService::uploadFile | response path: /tmp/workspace/88239/88239.png, code: 500, message: Internal Server Error

Impact#

  • Service: cupixworks-any-floorplan-agent
  • Team: nestle
  • 발생 횟수: 1
  • 최초 발생: 2026-06-16 20:18:45 KST
  • 최근 발생: 2026-06-16 20:18:45 KST

지난 7일간 동일 패턴이 1건 더 관찰됨 (2026-06-11 17:29:44 KST, floorplan 23315). 23315 는 이후 19:33:05 KST 에 SQS 재시도로 정상 성공함. 88239 도 SQS ApproximateReceiveCount: 1 로 첫 시도였으므로 재시도가 가능했을 것으로 보이나, 별도 후속 처리 로그는 동 7일 윈도우에서 추가 확인되지 않음 (uncertain — needs verification).

Root Cause Summary#

Presigned upload_url 로의 PUT 요청이 외부 서버(S3/upstream)로부터 일시적인 HTTP 500 (Internal Server Error) 응답을 받은 것이 1차 원인이다. 이는 즉각적인 코드 결함이 아니라 외부 의존성의 transient failure로 보인다. 다만 업로드 실패가 단순 transient 실패에 그치지 않고 2차 결함을 유발했다: request 라이브러리의 response 객체에는 .response 프로퍼티가 없는데, CupixAuth::handleErrorBaseService::getApiErrorToDeleteMessageec.response 접근을 가정하고 있어 정상적인 status-code 기반 에러 처리 경로를 타지 못한다. 여기에 더해 floorplan-service.tsuploadFile 콜백은 reject() 를 인자 없이 호출하여 상위 핸들러에 undefined 가 전파된다. 그 결과 (a) 의미 있는 에러 정보 없이 메시지 처리가 종료되고, (b) 어딘가에서 undefined.response 접근으로 추가 에러가 발생했다.

Technical Analysis#

Code Path#

  • Entry point: packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:51FloorplanService.run(targetId, msgObject)
  • 실행 흐름:
    1. floorplan-service.ts:75 — state 를 Processing 으로 업데이트
    2. floorplan-service.ts:76-80 — source PDF 다운로드
    3. floorplan-service.ts:81translateFloorplan (PDF → PNG, GraphicsMagick)
    4. floorplan-service.ts:82uploadFile(uploadUrl, localFilePath)failure point
  • Failure point: packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:295-300
packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:278-307typescript
private uploadFile = (url: string, path: string, headers?: any): Promise<void> => new Promise((resolve, reject) => {
    this.setErrorCode(ErrorCode.Agent.FloorplanFailedToUpload);
    logger.debug('FloorplanService::uploadFile | start path: %s, url: %s', path, url);
    const cupixAuth = this.cupixAuth;
    const sendReq = request.put(url, {
        headers: headers != undefined ? headers : {
            'X-CUPIX-AUTH': cupixAuth.accessToken
        },
        body: fs.readFileSync(path)
    });

    sendReq
        .on('complete', async () => { ... resolve(); })
        .on('response', res => {
            if (res.statusCode !== 200) {
                logger.error('FloorplanService::uploadFile | response path: %s, code: %d, message: %s', path, res.statusCode, res.statusMessage);
                cupixAuth.handleError(res);
                reject();   // ← 인자 없이 reject — 상위에 undefined 전파
            }
        })
        .on('error', err => {
            logger.error('FloorplanService::uploadFile | path: %s, error: %s', path, JSON.stringify(err));
            cupixAuth.handleError(err);
            reject();       // ← 동일하게 인자 없음
        });
});

handleErrorec.response 가 정의된 경우와 그렇지 않은 경우를 분기한다. 여기서 resrequest 라이브러리의 response 인스턴스로 statusCode/request 만 가지며 response 프로퍼티는 없다 → "Undefined response" 분기로 빠진다.

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

reject() 가 인자 없이 호출되므로 상위 BaseService.runByMessage 의 try/catch 에는 undefined 가 던져진다. 그 결과 handlingMessageErrors(error=undefined)getApiErrorToDeleteMessage(undefined) 로 진입하여 'undefined error' 만 로깅된다.

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

추가로 발생한 Cannot read properties of undefined (reading 'response') 에러의 정확한 발생 위치는 stack trace 가 함께 로깅되지 않아 단정할 수 없다 (uncertain — needs verification). 다만 error.response 형태로 접근하는 코드는 cupix-auth.ts:43, base-service.ts:249 등 다수가 있고, 본 케이스에서 error 가 undefined 로 전파되었다는 점이 가장 일관된 설명이다.

Log Evidence#

Datadog query (재현):

text
service:cupixworks-any-floorplan-agent (88239 OR "FloorplanService::uploadFile")
시간 범위: 2026-06-16T10:00:00Z ~ 2026-06-16T12:30:00Z

핵심 로그 시퀀스 (KST):

text
2026-06-16 20:18:19 info  BaseService::runByMessage | id: 88239
2026-06-16 20:18:22 info  FloorplanService::run | floorplan state: created, resource_state: created
2026-06-16 20:18:22 info  FloorplanService::run | cpFloorplanSource_downloadUrl: http://api-tesla.cupix.internal/api/v1/floorplan_sources/35955/download
2026-06-16 20:18:22 info  FloorplanService::translateFloorplan | source: /tmp/workspace/88239/source/35955.pdf, frame: 0, output: /tmp/workspace/88239/88239.png, width: 16384, height: 16384, dpi: 300, LOD: 6
2026-06-16 20:18:45 error FloorplanService::uploadFile | response path: /tmp/workspace/88239/88239.png, code: 500, message: Internal Server Error
2026-06-16 20:18:45 warn  CupixAuth::handleError | Undefined response: {"statusCode":500,"request":{"method":"PUT"}}
2026-06-16 20:18:45 error Cannot read properties of undefined (reading 'response')
2026-06-16 20:18:45 warn  BaseService::getApiErrorToDeleteMessage | undefined error
2026-06-16 20:18:45 info  BaseService::cleanUpAnythingRelatedModel | path: /tmp/workspace/88239
2026-06-16 20:18:45 error BaseService::handlingMessageErrors | Error and message object - {"error":"undefined error","sqsMessage":{"MessageId":"b8339230-8d79-4cfd-b491-7f9aa5be1be7","Attributes":{"ApproximateReceiveCount":"1"}}}

cupixworks-api 측에는 동일 시각 floorplan 88239 관련 에러 로그가 없음 (service:cupixworks-api status:error 88239 → 0건). 따라서 PUT 500 은 tesla API 가 아닌 외부 (S3 presigned URL 등) 응답으로 추정.

병행 처리된 floorplan 88240 (동일 source PDF 35955) 은 같은 시각 정상 성공:

text
2026-06-16 20:18:22 info  BaseService::runByMessage | id: 88240
2026-06-16 20:18:25 info  FloorplanService::translateFloorplan | source: /tmp/workspace/88240/source/35955.pdf ...
2026-06-16 20:18:49 info  FloorplanService::tileFloorplan | begin
2026-06-16 20:19:00 info  FloorplanService::tileFloorplan | tiling done and now save to `/tmp/workspace/88240/tile`

지난 7일 동일 에러 빈도 (service:cupixworks-any-floorplan-agent "FloorplanService::uploadFile" status:error):

text
2026-06-16 20:18:45 error FloorplanService::uploadFile | response path: /tmp/workspace/88239/88239.png, code: 500
2026-06-11 17:29:44 error FloorplanService::uploadFile | response path: /tmp/workspace/23315/23315.png, code: 500

→ 7일간 2건 (산발적, transient 패턴). 23315 는 같은 날 19:33:05 KST 에 재처리 성공 확인.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 presigned upload_url 의 외부(S3/internal upload endpoint) 서버에서 일시적 500 응답 CupixAuth::handleError{statusCode:500, request:{method:PUT}} 만 받음. cupixworks-api 측 88239 관련 에러 0건. 같은 시각 동일 source 로 처리한 88240 은 성공. 7일간 2건만 발생하는 산발적 패턴. 23315 는 재시도 시 정상 성공. Confirmed (transient upstream 5xx)
H2 uploadFile 콜백의 reject() 무인자 호출로 에러가 undefined 로 전파되어 처리/로깅 품질 저하 + 2차 NPE 유발 reject() 호출이 floorplan-service.ts:299, 305 에서 인자 없음. 로그상 "undefined error" 와 "Cannot read properties of undefined (reading 'response')" 가 같은 timestamp 에 동시 출현. 정확한 NPE 발생 file:line 은 stack trace 부재로 미확정 Confirmed (contributing)
H3 translateFloorplan / GraphicsMagick 단계 결함으로 손상된 PNG 가 업로드되어 서버가 500 응답 translateFloorplan 단계는 정상 info 로그로 종료. 같은 source PDF (35955) 로 변환한 88240 은 동일 파라미터(16384x16384, LOD 6) 로 정상 성공. Rejected
H4 인증 토큰 만료/누락으로 업로드 거부 401 이 아닌 500 응답. 업로드 직전 CupixAuth::setSession 정상 출력. 같은 시각 다른 floorplan 작업은 인증 통과. Rejected
H5 큰 파일 업로드 OOM/타임아웃 (16384x16384 PNG) request.putbody: fs.readFileSync(path) 로 메모리에 한꺼번에 로드 후 PUT — 잠재적 risk. 다만 본 발생 직전/직후 동일 크기 floorplan 88240 이 같은 노드/같은 시각에 성공. 동일 시각 동일 크기 정상 처리됨 Rejected (이 인스턴스에 한정)

Fix Recommendation#

즉시 조치 (Critical)#

  • 운영 측면에서 즉시 조치는 불필요. 본 발생은 transient 5xx 1건이며, SQS 가 메시지를 재시도 가능 상태로 유지한다 (ApproximateReceiveCount: 1). floorplan 88239 의 현재 상태가 Error 로 고정되었는지 확인하고 필요 시 수동 재처리 (uncertain — needs verification, 별도 floorplan state 확인 필요).

단기 개선 (1주 이내)#

  • packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:295-307uploadFile 에서 reject() 를 의미 있는 Error 객체로 호출하도록 수정한다. 최소한 reject(new Error(\uploadFile failed: ${res.statusCode} ${res.statusMessage}`))형태로 변경하여 상위BaseService::handlingMessageErrors` 가 nodejs Error 분기로 진입하고, "undefined error" 가 아닌 statusCode 기반 결정을 내릴 수 있게 한다.
  • 동시에 downloadFileWithHeader (line 258-275) 의 .on('response') 처리도 동일 패턴이므로 request 라이브러리 response 객체를 그대로 handleError 로 넘기지 말고, statusCode/statusMessage 를 가진 정상 Error 로 wrapping 하여 던지도록 정리한다.
  • 로깅 품질 개선: CupixAuth::handleErrorrequest response 인스턴스를 받았을 때 (즉 ec.statusCode 는 있으나 ec.response 는 없는 경우) 도 statusCode/uri 를 추출하여 의미 있는 warn 로그를 남기도록 분기 추가.

장기 개선 (재발 방지)#

  • presigned URL 업로드에 대해 idempotent retry 정책 도입 (예: 5xx/네트워크 에러에 한해 지수 백오프 재시도). 현재는 SQS visibility timeout 에 의존하므로 재시도 사이 지연이 길고 메시지가 DLQ 로 빠질 수 있다.
  • 16384x16384 PNG 를 fs.readFileSync + request.put 로 단일 버퍼 업로드하는 방식은 메모리 사용량 측면에서 risk 가 있다. fs.createReadStream(path).pipe(request.put(url)) 형태의 streaming 업로드로 전환 검토.
  • agent service 들에 unhandled promise rejection / global error 시 stack trace 가 같이 로그에 출력되도록 logger meta 보강. 본 RCA 에서 NPE 의 정확한 발생 file:line 을 단정할 수 없는 한계가 있음.

Monitoring#

writing-datadog-monitoring-queries 가이드에 따라 timeseries widget 용 쿼리:

  • floorplan agent 의 uploadFile 5xx 발생 추이:
text
service:cupixworks-any-floorplan-agent status:error "FloorplanService::uploadFile"
  • 2차 NPE 발생 추이:
text
service:cupixworks-any-floorplan-agent status:error "Cannot read properties of undefined (reading 'response')"
  • "undefined error" 로 SQS 메시지 처리가 모호하게 종료되는 패턴:
text
service:cupixworks-any-floorplan-agent status:error "BaseService::handlingMessageErrors" "undefined error"
  • 모든 agent 공통: handleError 가 Undefined response 분기로 빠진 빈도 (라이브러리 response 미스매치 신호):
text
service:cupixworks-any-floorplan-agent status:warn "CupixAuth::handleError | Undefined response"

알람 임계값은 1시간 sum > 5 또는 30분 내 동일 floorplan id 가 2회 이상 실패시.

Risk Assessment#

  • Risk level: low (단발성 transient 5xx, 7일간 2건, SQS 재시도로 자동 회복 가능)
  • 예상 복잡도: standard (uploadFile/handleError 의 reject 인자 보강 + 로깅 분기 추가)