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#
- 2026-06-16 20:18:19 KST —
BaseService::runByMessage | id: 88239— SQS 메시지 수신, 처리 시작 - 2026-06-16 20:18:22 KST —
FloorplanService::translateFloorplan시작 (source:35955.pdf, 16384x16384, LOD 6) - 2026-06-16 20:18:45 KST —
FloorplanService::uploadFilePUT 요청에 대한 HTTP 500 응답 수신 - 2026-06-16 20:18:45 KST —
CupixAuth::handleError | Undefined response: {"statusCode":500,"request":{"method":"PUT"}}(warn) - 2026-06-16 20:18:45 KST —
Cannot read properties of undefined (reading 'response')에러 (error) - 2026-06-16 20:18:45 KST —
BaseService::getApiErrorToDeleteMessage | undefined error(warn) →BaseService::handlingMessageErrors | Error and message object - {"error":"undefined error","sqsMessage":{...,"ApproximateReceiveCount":"1"}}
Error Log#
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::handleError 와 BaseService::getApiErrorToDeleteMessage 는 ec.response 접근을 가정하고 있어 정상적인 status-code 기반 에러 처리 경로를 타지 못한다. 여기에 더해 floorplan-service.ts 의 uploadFile 콜백은 reject() 를 인자 없이 호출하여 상위 핸들러에 undefined 가 전파된다. 그 결과 (a) 의미 있는 에러 정보 없이 메시지 처리가 종료되고, (b) 어딘가에서 undefined.response 접근으로 추가 에러가 발생했다.
Technical Analysis#
Code Path#
- Entry point:
packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:51—FloorplanService.run(targetId, msgObject) - 실행 흐름:
floorplan-service.ts:75— state 를Processing으로 업데이트floorplan-service.ts:76-80— source PDF 다운로드floorplan-service.ts:81—translateFloorplan(PDF → PNG, GraphicsMagick)floorplan-service.ts:82—uploadFile(uploadUrl, localFilePath)← failure point
- Failure point:
packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:295-300
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(); // ← 동일하게 인자 없음
});
});
handleError 는 ec.response 가 정의된 경우와 그렇지 않은 경우를 분기한다. 여기서 res 는 request 라이브러리의 response 인스턴스로 statusCode/request 만 가지며 response 프로퍼티는 없다 → "Undefined response" 분기로 빠진다.
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' 만 로깅된다.
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 (재현):
service:cupixworks-any-floorplan-agent (88239 OR "FloorplanService::uploadFile")
시간 범위: 2026-06-16T10:00:00Z ~ 2026-06-16T12:30:00Z
핵심 로그 시퀀스 (KST):
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) 은 같은 시각 정상 성공:
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):
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.put 의 body: 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-307의uploadFile에서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::handleError가requestresponse 인스턴스를 받았을 때 (즉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 발생 추이:
service:cupixworks-any-floorplan-agent status:error "FloorplanService::uploadFile"
- 2차 NPE 발생 추이:
service:cupixworks-any-floorplan-agent status:error "Cannot read properties of undefined (reading 'response')"
- "undefined error" 로 SQS 메시지 처리가 모호하게 종료되는 패턴:
service:cupixworks-any-floorplan-agent status:error "BaseService::handlingMessageErrors" "undefined error"
- 모든 agent 공통: handleError 가 Undefined response 분기로 빠진 빈도 (라이브러리 response 미스매치 신호):
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 인자 보강 + 로깅 분기 추가)