ES /docs

Cannot read properties of undefined (reading 'Levels')

RCA: Cannot read properties of undefined (reading 'Levels')

Overview#

What Happened#

2026-06-29 23:12 KST, cupixworks-any-room-agent (Cupix Tesla Room Agent) 가 BIM id 20418 메시지를 처리하던 중 자식 프로세스(RoomExtractorProcess)에서 Cannot read properties of undefined (reading 'Levels') TypeError 가 발생했다. SQS 메시지는 정상적으로 삭제되었지만 BIM 의 room/level 추출이 실패하여 BaseService::handlingMessageErrors로 "undefined response" 가 기록되었다. 단발성 1건이며 같은 큐의 직전(20417) / 직후(20419, 20420) BIM 은 모두 정상 처리되었다.

Quick Facts#

Field Value
exception.class TypeError
exception.message Cannot read properties of undefined (reading 'Levels')
top_frame /tmp/agent/dist/app.cjs:5367:26 (bundled ChildProcessManager.handleMessage rethrow site)
origin packages/cupix-tesla-room-agent/src/process/room-extractor.process.ts:58-62 (child process)
env production, us-west-2
affected bim_id 20418

Affected Teams#

Team / Domain Error Count Impact
gilbaneco (tenant: cupix) 1 1 BIM (id 20418) 의 room/level extraction 실패. BIM 의 room_stateExtracted 로 업데이트되지 못함 (코드 흐름이 runRoomExtractor 에서 throw 되어 후속 updateBimRoomState(...Extracted) 가 실행되지 않음).

Timeline#

  1. 2026-06-29 23:12:26 KSTBaseService::runByMessage | id: 20418 — BIM 20418 처리 시작 (RoomService::run).
  2. 2026-06-29 23:12:26 KSTCupixAuth::setSession | session_id: be789844db7da3a2eb3c1b42f86116929d1ee5fe — 세션 설정 완료, 이후 roomExtractManager.execute 호출.
  3. 2026-06-29 23:12:29 KST — 자식 프로세스에서 extract 가 undefined 인 상태로 extract.Levels 접근 → TypeError. 부모 프로세스 ChildProcessManager.handleMessagepending.reject(new Error(response.error)) 로 rethrow.
  4. 2026-06-29 23:12:29 KSTCupixAuth::handleError | Undefined response: { stack: "Error: Cannot read properties of undefined (reading 'Levels')..." } — axios response 가 없는 일반 Error 로 warn 로깅.
  5. 2026-06-29 23:12:29 KSTBaseService::handlingMessageErrors | Error and message object - {"error":"undefined response","sqsMessage":{"MessageId":"005426f3-..."}} — error 로깅 및 SQS 메시지 삭제 처리.
  6. 2026-06-29 23:30:12 KST — 다음 메시지 (BIM 20419) 정상 처리 (bim level size: 50, room size: 0).

Error Log#

Datadog Logs

text
Cannot read properties of undefined (reading 'Levels')

Datadog 원본 stack trace:

text
Error: Cannot read properties of undefined (reading 'Levels')
    at ChildProcessManager2.handleMessage (/tmp/agent/dist/app.cjs:5367:26)
    at ChildProcess.<anonymous> (/tmp/agent/dist/app.cjs:5295:16)
    at ChildProcess.emit (node:events:524:28)
    at ChildProcess.emit (node:domain:489:12)
    at emit (node:internal/child_process:950:14)
    at process.processTicksAndRejections (node:internal/process/task_queues:83:21)

Impact#

  • Service: cupixworks-any-room-agent (repo: cupixworks, package: cupix-tesla-room-agent)
  • Team: gilbaneco
  • 발생 횟수: 1
  • 최초 발생: 2026-06-29 23:12 KST
  • 최근 발생: 2026-06-29 23:12 KST

영향 범위는 단일 BIM (id 20418) 으로 제한된다. 동일 큐의 인접 메시지(20417, 20419, 20420)는 정상 처리되었으므로 인프라/배포 수준의 회귀가 아니라 특정 BIM input 데이터에 한정된 결함으로 판단된다.

Root Cause Summary#

RoomExtractorProcess.execute 가 ADF/Forge 모델에서 room/level 정보를 추출할 때, 외부 라이브러리(RoomExtractor.room_extract — svf1 path, 또는 ForgeAgent.extract — svf2 path) 호출 결과를 받아 extract.Levels 를 그대로 읽는데, BIM 20418 입력에 대해 라이브러리가 undefined 를 반환(또는 throw 후 swallow)했고 그 결과 extract 변수가 undefined 상태에서 extract.Levels 접근이 TypeError 를 일으켰다. 자식 프로세스의 try/catch 가 에러를 sendError 로 부모에게 전달했고, 부모 ChildProcessManager.handleMessagenew Error(response.error) 로 재생성하여 stack 이 부모 라인을 가리키게 되었다. 정상 경로에서 동작한 다른 BIM 들과 달리 이 특정 BIM 의 Forge URN/format 에 대해 라이브러리가 빈/잘못된 응답을 돌려준 것이 직접 원인이다.

Technical Analysis#

Code Path#

  • Entry point: applications/agents/packages/cupix-tesla-room-agent/src/room-service.ts:58 (RoomService.run)
  • runRoomExtractorRoomExtractManager.execute 가 자식 프로세스에 execute 메시지 전송
  • 자식 프로세스 처리: applications/agents/packages/cupix-tesla-room-agent/src/process/room-extractor.process.ts:29-65
  • Failure point: applications/agents/packages/cupix-tesla-room-agent/src/process/room-extractor.process.ts:58-62extract 가 undefined 인 상태에서 extract.Levels 접근
  • Rethrow site (stack trace 가 가리키는 곳): applications/agents/packages/base/src/manager/child-process.manager.ts:182pending.reject(new Error(response.error || 'Unknown error'))

자식 프로세스의 실패 지점:

applications/agents/packages/cupix-tesla-room-agent/src/process/room-extractor.process.ts:29-65typescript
private async execute(params: any): Promise<any> {
  try {
    this.log('RoomExtractorProcess::execute | start');

    let extract: {
      Rooms?: ForgeAgent.IRoom[] | RoomExtractor.Room[];
      Levels?: ForgeAgent.IRoomLevel[] | RoomExtractor.Level[];
      Meta?: ForgeAgent.IMeta;
    };

    if (params.formatType === 'svf2') {
      const result = await ForgeAgent.extract(params.apiConfig, params.urn, {
        extractRoom: true,
        extractMeta: true,
        region: params.region
      });

      if (result.Error == undefined) {
        extract = result;
      } else if (result.Error === ForgeAgentErrorCode.FORGE_SOURCE_INVALID_MANIFEST) {
        this.log('RoomExtractorProcess::execute - svf2 manifest is invalid, try svf1 process');
        extract = await RoomExtractor.room_extract(params.token, params.urn);
      } else {
        throw new Error('Failed to get rooms from svf2');
      }
    } else {
      extract = await RoomExtractor.room_extract(params.token, params.urn);
    }

    const extractorResults = {
      Levels: extract.Levels,   // ← extract 가 undefined 면 여기서 TypeError
      Rooms: extract.Rooms,
      Meta: extract.Meta
    };

    this.log('RoomExtractorProcess::execute | Room extraction completed successfully');
    return extractorResults;
  } catch (error) {
    ...

핵심: 라인 50 / 55 의 RoomExtractor.room_extract(params.token, params.urn) 호출에서 params.token 은 manager 가 보낸 params 에 존재하지 않는 필드이다(RoomExtractorParams 인터페이스는 apiConfig, urn, formatType, region 만 정의):

applications/agents/packages/cupix-tesla-room-agent/src/manager/room-extract.manager.ts:58-66typescript
export interface RoomExtractorParams {
  apiConfig: {
    clientId: string;
    clientSecret: string;
  }
  urn: string;
  formatType: string;
  region: string;
}

따라서 svf1 fallback (또는 formatType !== 'svf2') 경로로 진입하면 params.token === undefined 인 상태로 room_extract 가 호출된다. 라이브러리가 token undefined 인 경우 (a) undefined 를 반환하거나 (b) 빈 에러로 swallow 한 결과 extractundefined 상태가 되면 라인 58 extract.Levels 가 TypeError 를 던진다. (uncertain — 라이브러리 내부 구현은 본 RCA 범위 밖이므로 "라이브러리가 undefined 를 반환"하는 분기는 단정할 수 없음. 그러나 결과 extract 가 undefined 가 된 사실은 stack trace 의 reading 'Levels' 메시지로부터 확정적.)

부모 측에서 stack 이 rethrow 되는 위치:

applications/agents/packages/base/src/manager/child-process.manager.ts:154-184typescript
private handleMessage(response: MessageResponse): void {
  if (response.type === 'ready') { this.emit('ready'); return; }
  if (response.type === 'log') { logger.debug(`[Child] ${response.data}`); return; }
  if (response.type === 'log_error') { logger.error(`[Child] ${response.data}`); return; }

  const pending = this.pendingMessages.get(response.id);
  if (!pending) {
    logger.warn(`ChildProcessManager::handleMessage | No pending message for ID: ${response.id}`);
    return;
  }

  this.pendingMessages.delete(response.id);
  if (pending.timer) clearTimeout(pending.timer);

  if (response.success) {
    pending.resolve(response.data);
  } else {
    pending.reject(new Error(response.error || 'Unknown error'));   // ← stack trace 가 가리키는 라인
  }
}

자식이 sendError(message.id, error) 로 보낸 메시지를 부모가 받아 new Error(...) 로 다시 던진다. 따라서 Datadog 에 찍힌 stack trace 의 top frame 은 원래 발생 지점이 아니라 IPC 경계의 재생성 지점이다. 원인 분석에는 자식 측 코드(room-extractor.process.ts)를 봐야 한다.

Log Evidence#

Datadog 쿼리 (재현용):

text
service:cupixworks-any-room-agent "Levels"

핵심 로그 시퀀스 (시간 오름차순, KST):

text
2026-06-29 23:12:26  info   BaseService::runByMessage | id: 20418
2026-06-29 23:12:26  info   CupixAuth::setSession | session_id: be789844db7da3a2eb3c1b42f86116929d1ee5fe
2026-06-29 23:12:26  info   RoomService::run | forge-agents version: 10.99.0, forge-utils version: 10.99.0
2026-06-29 23:12:29  error  Cannot read properties of undefined (reading 'Levels')
2026-06-29 23:12:29  warn   Cannot read properties of undefined (reading 'Levels')
2026-06-29 23:12:29  warn   CupixAuth::handleError | Undefined response: {"stack":"Error: Cannot read properties of undefined (reading 'Levels')\n    at ChildProcessManager2.handleMessage (/tmp/agent/dist/app.cjs:5367:26)\n    at ChildProcess.<anonymous> (/tmp/agent/dist/app.cjs:5295:16)\n    at ChildProcess.emit (node:events:524:28)\n    at ChildProcess.emit (node:domain:489:12)\n    at emit (node:internal/child_process:950:14)\n    at process.processTicksAndRejections (node:internal/process/task_queues:83:21)","message":"Cannot read properties of undefined (reading 'Levels')"}
2026-06-29 23:12:29  info   AwsQueueManager::deleteMessage | begin - queue url: https://sqs.us-west-2.amazonaws.com/002596530511/cupix-tesla-room-agent-production
2026-06-29 23:12:29  info   BaseService::cleanUpAnythingRelatedModel | path: /tmp/workspace/20418
2026-06-29 23:12:29  error  BaseService::handlingMessageErrors | Error and message object - {"error":"undefined response","sqsMessage":{"MessageId":"005426f3-53b8-40be-bd88-35d51c2510f8","Attributes":{"ApproximateReceiveCount":"1"}}}
2026-06-29 23:12:29  info   AwsQueueManager::deleteMessage | end - message id: 005426f3-53b8-40be-bd88-35d51c2510f8

전후 메시지 비교 — 정상 처리된 인접 BIM 들은 CPBim::fromRoomExtractor | bim level size: N, room size: 0 가 정상 출력됨:

text
2026-06-29 23:03:33  info   BaseService::runByMessage | id: 20417
2026-06-29 23:03:58  info   CPBim::fromRoomExtractor | bim level size: 5, room size: 0
2026-06-29 23:30:12  info   BaseService::runByMessage | id: 20419
2026-06-29 23:30:58  info   CPBim::fromRoomExtractor | bim level size: 50, room size: 0
2026-06-29 23:32:03  info   BaseService::runByMessage | id: 20420
2026-06-29 23:32:21  info   CPBim::fromRoomExtractor | bim level size: 55, room size: 0

20418 에 대해서만 CPBim::fromRoomExtractor 로그가 누락 — 즉 runRoomExtractor 가 cpBim 의 결과를 채우기 전에 throw 되었음. 부분 처리 (updateBimRoomState(...Extracting) 이후 Extracted 미도달) 가 발생했음을 의미한다.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 자식 프로세스의 room-extractor.process.ts:58 에서 extract 가 undefined 인 상태로 extract.Levels 가 호출되어 TypeError 가 발생했고, 부모 ChildProcessManager.handleMessage 가 IPC 경계에서 rethrow 한 것 stack message 가 정확히 'Levels' 를 가리킴; bundled stack top 이 부모 handleMessage (rethrow site, child-process.manager.ts:182); 자식의 try/catch 가 모든 에러를 sendError 로 위임; 20418 만 CPBim::fromRoomExtractor 로그가 누락 (extractor 결과를 받지 못함) Confirmed
H2 cpbim.ts:78this.roomExtractorResults?.Levels.lengthLevels 가 없는 결과 객체를 받아 throw 같은 키 이름 Levels 가 등장; 결과 형태가 비표준일 가능성 Optional chain ?.roomExtractorResults 가 undefined 면 short-circuit; 객체가 있고 .Levels 가 없으면 .length 에서 throw 되어 메시지가 'length' 가 되어야 함. 또한 cpbim 의 fromRoomExtractor 가 호출되었다면 그 직전 `RoomExtractorProcess::execute Room extraction completed successfully` 자식 log 가 나와야 하나 부재
H3 외부 의존성 outage (Forge / Autodesk API) 동시간대 다수 클러스터가 폭증해야 함 status-board: dep:* active 없음, 동일 서비스 인접 메시지 정상 처리, 단발성 1건 Rejected
H4 배포 회귀 (forge-agents 10.99.0 신규 도입) 버전 정보 로그 존재 동일 버전으로 20417/20419/20420 모두 정상 처리 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • applications/agents/packages/cupix-tesla-room-agent/src/process/room-extractor.process.ts:46-65 에서 extract 가 undefined 인 케이스를 명시적으로 처리한다. 두 가지 보강이 필요:
    1. result 자체에 대한 nil guard (svf2 path 라인 46 의 result.Error == undefined 직전에 result == undefined 분기 처리)
    2. svf1 path (RoomExtractor.room_extract 호출) 후 extract == undefined 체크 추가 후 의미 있는 에러 throw — 예: throw new Error(\RoomExtractor.room_extract returned undefined for urn=${params.urn}, formatType=${params.formatType}`)`
  • 동시에 RoomExtractor.room_extract(params.token, params.urn) 호출이 사용하는 params.tokenRoomExtractorParams 인터페이스에 존재하지 않는 필드다 (room-extract.manager.ts:58-66). svf1 fallback 자체가 항상 undefined token 으로 호출되고 있을 가능성이 높다 — manager 의 params 정의 또는 process 의 호출 시그니처 중 어느 쪽이 옳은지 확인하고 정합성을 맞춰야 한다. (uncertain — 라이브러리가 token 을 옵션으로 허용한다면 영향 없을 수 있음; 확인 필요.)

단기 개선 (1주 이내)#

  • room-extractor.process.ts:66-77 의 catch 가 자식 측 stack 을 그대로 sendError 에 넘기지 못해(파라미터가 Error.message 만 사용됨, base.process.ts:56-59) 부모 측에서 원본 stack 이 유실된다. BaseProcess.sendError 가 error.stack 까지 전송하도록 확장하고, ChildProcessManager.handleMessage (child-process.manager.ts:182) 가 받은 stack 을 새 Error 의 stack 에 부착하면 향후 동일 사고에서 IPC 경계 너머의 원인 라인이 Datadog 에 그대로 노출된다.
  • RoomService.run (room-service.ts:58) 에서 runRoomExtractor 실패 시 BIM 의 room_stateExtracting 상태로 멈춘 채 SQS 가 삭제되는 흐름을 점검. updateErrorState (line 512-517) 가 호출되는지, 또는 별도 보상 로직이 필요한지 확인.

장기 개선 (재발 방지)#

  • 외부 추출 라이브러리(@cupix/app-room-extractor-v2, @cupix/forge-agents) 의 반환 계약(nullable / throw 여부)을 타입 수준에서 강화. 현재 extract 의 타입이 inline literal 이라 nil 가능성을 컴파일러가 잡지 못한다. RoomExtractor.room_extractForgeAgent.extractPromise<X | undefined> 인지 Promise<X> 인지 시그니처를 확정하고 호출 측을 그에 맞춰 보강한다.
  • 같은 패턴(자식 IPC 결과 객체에서 .X 무방비 접근)이 다른 agent (cupix-tesla-bim-revision-agent, cupix-tesla-mesh-agent, cupix-skat-master-agentChildProcessManager 사용처) 에도 있는지 sweep — applications/agents/packages/*/src/process/*.process.ts 의 결과 직조립 부분을 일괄 점검 권장.

Monitoring#

Datadog dashboard widget 용 timeseries 쿼리 (writing-datadog-monitoring-queries 가이드를 따라 monitor-only 문법은 사용하지 않음):

해당 에러 패턴 발생 건수:

text
sum:trace.express.request.errors{service:cupixworks-any-room-agent}.as_count()

서비스의 전체 에러 로그 건수 (log-based metric 이 설정되어 있을 경우):

text
sum:logs.error.count{service:cupixworks-any-room-agent}.as_count()

(uncertain — log-based metric logs.error.count 가 이 organization 에 설정되어 있는지 확인 필요. 없으면 Datadog UI 에서 service:cupixworks-any-room-agent status:error saved query 기반 monitor 로 대체.)

추가 권장 알림:

  • RoomService::run 실패율 > 1% (5분 window) 시 page
  • 동일 stack frame room-extractor.process.ts + 'Levels' 메시지 재발생 시 즉시 알림 (수정 효과 검증용)

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial (즉시 조치: 자식 프로세스 nil guard 2곳 추가). 단기 개선의 stack 전파 개선은 standard.
  • 영향 범위가 단일 BIM (20418) 이고 재시도/주변 메시지 처리에 회귀 없음. 다만 같은 종류의 input 이 다시 들어오면 동일 실패가 재현될 가능성이 있으므로 nil guard 보강은 필요.