ForgeService::handlingMessageErrors | Error and message object - {"error":"undefined response","sqsM
RCA: ForgeService::handlingMessageErrors | undefined response
Overview#
What Happened#
2026-05-07 22:30 UTC에 cupixworks-any-forge-agent 서비스가 Autodesk Forge로의 파일 업로드 도중 ECONNRESET(TCP 연결 초기화)를 수신했다. 에러 핸들러가 이 네트워크 에러를 "undefined response"로 분류하면서 SQS 메시지를 삭제하고 BIM forge_state를 Error로 마킹했다. 단, 10분 후 동일 BIM 모델(3380)의 다음 리비전(4746) 처리는 정상 완료되어 일시적 네트워크 장애로 확인된다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | ECONNRESET (via ForgeApi::uploadStream) |
| exception.message | undefined response |
| top_frame | forge-service.ts:412 |
| env | production, ap-southeast-2 |
Timeline#
- 22:29:55Z — S3Manager 초기화, 리전 ap-southeast-2
- 22:29:56Z —
runByMessage시작 (bim_id: 3380, bim_revision_id: 4745) - 22:30:43.367Z —
ForgeApi::uploadStreamECONNRESET 에러 발생 - 22:30:43.372Z —
getApiErrorToDeleteMessage에서 "undefined response"로 분류 - 22:30:43.456Z — SQS 메시지 삭제 완료
- 22:30:43.728Z —
handlingMessageErrors최종 에러 로그 기록
Error Log#
ForgeService::handlingMessageErrors | Error and message object - {"error":"undefined response","sqsMessage":{"MessageId":"53cf7eff-3bfe-4b49-bb56-c933161ad8e1","Attributes":{"ApproximateReceiveCount":"1"}}}
Impact#
- Service:
cupixworks-any-forge-agent - 발생 횟수: 1
- 최초 발생: 2026-05-07T22:30:43.728Z
- 최근 발생: 2026-05-07T22:30:43.728Z
- 영향 범위: team unispace (id: 158), BIM revision 4745의 Forge 변환 실패. 동일 BIM 모델의 후속 리비전은 정상 처리됨.
Root Cause Summary#
Autodesk Forge signed S3 URL로의 파일 업로드 중 원격 서버가 TCP 연결을 리셋(ECONNRESET)했다. ForgeApi::uploadStream이 reject(err)로 에러를 전파하면, uploadFileSignedS3의 Promise reject를 거쳐 handlingMessageErrors에 도달한다. 문제는 getApiErrorToDeleteMessage의 에러 분류 로직에 있다: ECONNRESET 에러가 {code: "ECONNRESET"}만 가진 객체로 전달될 때, Node.js system error 판별 조건(errno && code && syscall 모두 존재)을 통과하지 못하고, .response 속성도 없어 "undefined response"로 분류된다. 이 반환값이 truthy이므로 SQS 메시지가 삭제되어 재시도가 불가능해진다. 일시적 네트워크 에러임에도 영구 실패로 처리되는 것이 핵심 문제이다.
Technical Analysis#
Code Path#
- Entry point:
forge-service.ts:390—handlingMessageErrors - Error classification:
forge-service.ts:342—getApiErrorToDeleteMessage - Upload failure origin:
forge-api.ts:129—uploadStreamerror handler
1. 업로드 스트림에서 ECONNRESET 발생
request.put으로 signed S3 URL에 파일 청크를 업로드하는 중 TCP 연결이 리셋된다:
private uploadStream = (signedUrl: string, stream: fs.ReadStream, length: number): Promise<any> => new Promise((resolve, reject) => {
stream.pipe(request.put(signedUrl, {
headers: {
'Content-Type': 'application/octet-stream',
'Content-Length': length
}
}).on('response', (res) => {
const data: any = {};
if (res.statusCode === 200) {
data.eTag = res.headers.etag?.replace(/"/g, '');
resolve(data);
} else {
logger.error('ForgeApi::uploadStream | end, response error: %s', JSON.stringify(res));
reject();
}
})).on('error', err => {
logger.error('ForgeApi::uploadStream | end, error: %s', JSON.stringify(err));
reject(err); // err = {code: "ECONNRESET"} — errno/syscall 없이 전달됨
});
});
request 라이브러리의 .on('error') 이벤트에서 발생하는 에러 객체는 Node.js 내장 Error가 아닌 축약된 형태({code: "ECONNRESET"})로 전달될 수 있다.
2. 에러 분류 로직의 허점
private getApiErrorToDeleteMessage = (error: any): any => {
if (error == undefined) {
logger.warn('ForgeService::getApiErrorToDeleteMessage | undefined error');
return 'undefined error';
}
if (error.errno != undefined && error.code != undefined && error.syscall != undefined) {
logger.warn('ForgeService::getApiErrorToDeleteMessage | nodejs common system error - %s', JSON.stringify(error));
return; // undefined 반환 → 메시지 유지 (재시도 가능)
}
const response = CPUtils.isJsonString(error) ? JSON.parse(error) : error.response;
if (response == undefined) {
logger.warn('ForgeService::getApiErrorToDeleteMessage | undefined response - %s', JSON.stringify(error));
return 'undefined response'; // truthy 반환 → 메시지 삭제 (재시도 불가)
}
// ...
};
ECONNRESET 에러는 {code: "ECONNRESET"}만 가지고 있어:
error.errno= undefined → system error 조건 불충족 (line 347)error.response= undefined → "undefined response" 반환 (line 354)
3. 메시지 삭제 및 상태 업데이트
const apiErrorObject = this.getApiErrorToDeleteMessage(error);
if (apiErrorObject != undefined || this.checkReceiveCountToDeleteMessage()) {
try {
errorAndMessage.error = apiErrorObject;
await this.deleteByMessage(this.messageInProcess);
if (this._cpBimInProcess != undefined) await this.updateBimForgeState(this._cpBimInProcess.id, TESLA.UpdateBimRequest.ForgeStateEnum.Error);
}
apiErrorObject가 'undefined response' (truthy)이므로 조건문을 통과하여 SQS 메시지 삭제 + BIM forge_state를 Error로 마킹한다.
Log Evidence#
검색 쿼리:
service:cupixworks-any-forge-agent status:error @environment:production
time: 2026-05-07T21:30:00Z to 2026-05-07T23:00:00Z
에러 직전 이벤트 체인:
22:30:43.367Z [error] ForgeApi::uploadStream | end, error: {"code":"ECONNRESET"}
22:30:43.370Z [warn] ForgeApi::uploadFileSignedS3 | {"code":"ECONNRESET"}
22:30:43.372Z [warn] ForgeService::getApiErrorToDeleteMessage | undefined response - {"code":"ECONNRESET"}
22:30:43.373Z [info] AwsQueueManager::deleteMessage | begin
22:30:43.456Z [info] AwsQueueManager::deleteMessage | end - message id: 53cf7eff-3bfe-4b49-bb56-c933161ad8e1
22:30:43.726Z [info] ForgeService::cleanUpAnythingRelatedModel | path: /tmp/workspace/3380
22:30:43.728Z [error] ForgeService::handlingMessageErrors | Error and message object - {"error":"undefined response","sqsMessage":{"MessageId":"53cf7eff-3bfe-4b49-bb56-c933161ad8e1","Attributes":{"ApproximateReceiveCount":"1"}}}
동일 BIM 모델의 후속 성공 처리 (10분 후):
22:40:xx.xxxZ [info] ForgeService::runByMessage | bim_id: 3380 bim_revision_id: 4746
... (업로드 정상 완료, forge object 생성, workflow 시작)
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | ECONNRESET가 getApiErrorToDeleteMessage에서 retriable로 인식되지 못해 메시지가 영구 삭제됨 |
로그에서 {code:"ECONNRESET"}가 errno/syscall 없이 전달됨 확인 (22:30:43.372Z). request 라이브러리가 축약된 에러 객체 전달. |
— | Confirmed |
| H2 | Autodesk Forge API 자체의 장기 장애 | 10분 후 동일 BIM 모델의 다음 리비전이 정상 처리됨 | 단일 발생, 후속 성공 | Rejected |
| H3 | 파일 크기나 형식 문제로 업로드 실패 | — | 동일 BIM 모델의 후속 리비전 성공. ECONNRESET는 파일 내용과 무관한 네트워크 에러 | Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 파일:
applications/agents/packages/cupix-tesla-forge-agent/src/forge-service.ts:347-354 - 방향:
getApiErrorToDeleteMessage에서error.code가 네트워크 관련 코드(ECONNRESET,ECONNREFUSED,ETIMEDOUT,ENOTFOUND,EPIPE)인 경우에도undefined를 반환하여 메시지를 유지(재시도 가능)하도록 분류 조건을 확장한다. 현재는errno && code && syscall세 속성이 모두 있어야 system error로 인식하지만,request라이브러리가errno/syscall없이code만 전달하는 경우가 있다.
단기 개선 (1주 이내)#
uploadStream에서 ECONNRESET 발생 시 자체적으로 재시도 로직(exponential backoff, 최대 3회)을 추가하여 일시적 네트워크 장애를 에러 핸들러까지 전파하지 않도록 한다.reject(err)시 에러 객체를 정규화하여errno,code,syscall을 모두 포함하도록 래핑하는 것도 대안이다.
장기 개선 (재발 방지)#
request라이브러리는 deprecated 상태이므로axios또는undici로 마이그레이션하면 에러 객체 형식이 일관되고 retry/timeout 설정이 용이하다 (이미axios를 import하고 있으므로 통합 가능).- 에러 분류 로직을 화이트리스트(삭제할 에러) 방식에서 블랙리스트(삭제하지 않을 에러) 방식으로 전환하여, 알 수 없는 에러는 기본적으로 재시도하도록 안전하게 변경.
Monitoring#
- ECONNRESET 빈도 모니터링:
service:cupixworks-any-forge-agent "ECONNRESET" status:error
- "undefined response"로 인한 메시지 삭제 추적:
service:cupixworks-any-forge-agent "undefined response" "deleteMessage"
- BIM forge_state Error 상태 전환 빈도 대시보드 추가 권장
Risk Assessment#
- Risk level: low
- 예상 복잡도: standard
- 단일 발생이며 후속 리비전은 정상 처리됨. 다만 동일 패턴이 반복될 경우 사용자의 BIM 변환이 수동 재시도 없이 실패하므로, 에러 분류 로직 수정이 권장됨.