ES /docs

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#

  1. 22:29:55Z — S3Manager 초기화, 리전 ap-southeast-2
  2. 22:29:56ZrunByMessage 시작 (bim_id: 3380, bim_revision_id: 4745)
  3. 22:30:43.367ZForgeApi::uploadStream ECONNRESET 에러 발생
  4. 22:30:43.372ZgetApiErrorToDeleteMessage에서 "undefined response"로 분류
  5. 22:30:43.456Z — SQS 메시지 삭제 완료
  6. 22:30:43.728ZhandlingMessageErrors 최종 에러 로그 기록

Error Log#

Datadog Logs

text
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::uploadStreamreject(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:390handlingMessageErrors
  • Error classification: forge-service.ts:342getApiErrorToDeleteMessage
  • Upload failure origin: forge-api.ts:129uploadStream error handler

1. 업로드 스트림에서 ECONNRESET 발생

request.put으로 signed S3 URL에 파일 청크를 업로드하는 중 TCP 연결이 리셋된다:

applications/agents/packages/api/src/forge-api.ts:113-132typescript
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. 에러 분류 로직의 허점

applications/agents/packages/cupix-tesla-forge-agent/src/forge-service.ts:342-355typescript
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. 메시지 삭제 및 상태 업데이트

applications/agents/packages/cupix-tesla-forge-agent/src/forge-service.ts:400-405typescript
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#

검색 쿼리:

text
service:cupixworks-any-forge-agent status:error @environment:production
time: 2026-05-07T21:30:00Z to 2026-05-07T23:00:00Z

에러 직전 이벤트 체인:

text
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분 후):

text
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 빈도 모니터링:
text
service:cupixworks-any-forge-agent "ECONNRESET" status:error
  • "undefined response"로 인한 메시지 삭제 추적:
text
service:cupixworks-any-forge-agent "undefined response" "deleteMessage"
  • BIM forge_state Error 상태 전환 빈도 대시보드 추가 권장

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard
  • 단일 발생이며 후속 리비전은 정상 처리됨. 다만 동일 패턴이 반복될 경우 사용자의 BIM 변환이 수동 재시도 없이 실패하므로, 에러 분류 로직 수정이 권장됨.