ES /docs

ChildProcessManager::setupEventHandlers | Child process stderr: ForgeUtilsError: Resource not found:

RCA: ChildProcessManager stderr — ForgeUtilsError: Resource not found (AECModelData.json)

Overview#

What Happened#

2026-06-24 13:44 KST에 cupixworks-any-bimrevision-agent (us-west-2, production)가 BIM revision id=24593 (bim_id=18044, V24 → V23) 비교 작업을 수행하던 중 child process BimCompareProcess가 Autodesk Forge에서 AECModelData.json 파일을 가져오지 못해 ForgeUtilsError: Resource not found를 stderr로 출력했다. 같은 child-process 실패의 stderr 라인들이 부모의 ChildProcessManager::setupEventHandlers에서 각각 한 줄씩 error 레벨로 기록되어 동일한 fingerprint 클러스터로 묶였다. 동일 시각대에 20개의 클러스터(스택프레임마다 하나씩)가 함께 터졌으며 status board는 이를 cupixworks-any-bimrevision-agent 서비스 degradation 인시던트로 자동 묶었다.

Quick Facts#

Field Value
exception.class ForgeUtilsError (errorCode: API_NOT_FOUND)
exception.message Resource not found: file/urn%3Aadsk.fluent%3Afs.file%3Aautodesk-360-translation-storage-prod%2Fw3cEko2vSNuSmggy9xLMHQ%2F21%2Foutput%2F0%2FAECModelData.json
top_frame @cupixapps/forge-utils/server-utils/forge-client.js:90 (ForgeClient.handleApiError)
runtime Node.js child process spawned by ChildProcessManager (/tmp/agent/dist/process/bim-compare.process.cjs)
deploy @cupix/forge-agents@10.98.0, @cupixapps/forge-utils@10.98.0
env production, region us-west-2, tenant cupix, team gad

Affected Teams#

Team / Domain Error Count Impact
gad / BIM revision pipeline 3 (this cluster) + 17 sibling stack-frame clusters = 20 logs in 3분 윈도우 단일 BIM revision (bim_id=18044, bim_revision_id=24593) 비교 실패. revision state가 Error로 마킹되어 사용자는 V24 비교 결과를 받지 못함. 다른 revision 작업은 영향 없음

Timeline#

  1. 2026-06-24 13:44:03 KSTAwsS3Manager 초기화, BIM revision 메시지(id 24593) 처리 시작
  2. 2026-06-24 13:44:05 KST — child process spawn, AWS SDK v2 maintenance 경고가 stderr로 출력되어 error 레벨 로그 5건 생성
  3. 2026-06-24 13:44:07 KSTBimRevisionService::REVISE-BEGIN (src_forge_urn version 21, prev_forge_urn version 20), runBimCompare 호출
  4. 2026-06-24 13:44:08 KSTForgeExtractor.loadAECBIMSVF2Manifest.AECModelDataClient.getData 가 Forge에서 version 21의 AECModelData.json 404 응답, ForgeUtilsError: Resource not found throw (first_seen)
  5. 2026-06-24 13:44:10 KSTCompareExtractor.compare 경로에서 동일 asset 재호출, 같은 404로 두 번째 stderr 덤프 (last_seen)
  6. 2026-06-24 13:44:10 KST — child process exit, BimCompareManager::execute catch → BimRevisionExtractorExecute 에러 코드 설정 → BimRevisionService::run catch → revision state를 Error로 업데이트
  7. 2026-06-24 13:47:03 KST — 같은 서비스의 후속 BIM revision job들이 시작되며 다시 AWS SDK v2 maintenance 경고 stderr가 흘러 같은 fingerprint 클러스터들의 last_event_at을 갱신, status board는 이 시점을 resolved_at으로 기록

Error Log#

Datadog Logs

text
ChildProcessManager::setupEventHandlers | Child process stderr: ForgeUtilsError: Resource not found: file/urn%3Aadsk.fluent%3Afs.file%3Aautodesk-360-translation-storage-prod%2Fw3cEko2vSNuSmggy9xLMHQ%2F21%2Foutput%2F0%2FAECModelData.json?acmsession=dXJuOmFkc2sud2lwcHJvZDpmcy5maWxlOnZmLnczY0VrbzJ2U051U21nZ3k5eExNSFE_dmVyc2lvbj0yMQ

Impact#

  • Service: cupixworks-any-bimrevision-agent
  • Team: gad
  • 발생 횟수: 3 (this cluster) — 동일 incident에 묶인 20개 클러스터의 합산 stderr 라인 수
  • 최초 발생: 2026-06-24 13:44:08 KST
  • 최근 발생: 2026-06-24 13:44:10 KST

영향은 단일 BIM revision 한 건에 국한된다. BimRevisionService::run의 catch 블록(bim-revision-service.ts:188-191)이 에러를 잡아 revision state를 Error로 업데이트하므로 worker process가 죽거나 다른 revision 작업이 차단되지는 않는다. 사용자 가시 영향은 bim_revision_id=24593 (V24, bim_id=18044)의 비교 결과를 볼 수 없다는 점뿐이다.

Root Cause Summary#

근본 원인은 Autodesk Forge Model Derivative 서비스가 source URN의 version 21에 대해 AECModelData.json derivative를 반환하지 못한 것 (HTTP 404 → errorCode: 'API_NOT_FOUND')이다. BimRevisionService::runBimComparecpBimRevision.forgeUrn(version 21)을 그대로 child process로 전달하고, @cupixapps/forge-utilsModelDataClient.getBufferForgeClient.handleApiError가 404를 ForgeUtilsError로 변환하여 throw한다. child 쪽에서 throw된 에러 객체는 IPC로 부모에게 결과로 돌아가는 한편, Node.js의 unhandled rejection / pre-exit 출력으로 인해 동일한 스택트레이스가 stderr로 한 번 더 흘러나오고, 부모의 ChildProcessManager.setupEventHandlers (child-process.manager.ts:138-141)가 stderr 데이터를 라인 단위로 logger.error로 기록한다. 그 결과 한 번의 비즈니스 실패가 스택 프레임 수만큼의 별개 에러 로그로 폭증해 클러스터 20개를 한꺼번에 생성했다.

즉, 비즈니스 실패의 1차 원인은 Forge 외부 API의 API_NOT_FOUND (translation manifest 미생성 또는 만료 가능성)이고, 로그 폭증의 2차 원인은 child process stderr를 라인 단위로 error 레벨에 매핑하는 ChildProcessManager의 로깅 정책이다.

Technical Analysis#

Code Path#

  • Entry point (parent): bim-revision-service.ts:603runBimCompare
  • Parent → child IPC: bim-compare.manager.ts:65childProcessManager.execute<ForgeAgent.Result>('execute', params)
  • Child entry: bim-compare.process.ts:67BimCompareProcess.executeForgeAgent.extract(...)
  • Failure call site (vendored): @cupixapps/forge-utils/server-utils/forge-client.js:90ForgeClient.handleApiError
  • stderr→logger bridge (where the error log fingerprint is generated): child-process.manager.ts:138-141

부모는 정상적으로 catch한다 (bim-revision-service.ts:188-191):

applications/agents/packages/cupix-tesla-bim-revision-agent/src/bim-revision-service.ts:188-191typescript
} catch (ec: any) {
    logger.error('BimRevisionService::run | error: %s', JSON.stringify(ec.message));
    await this.updateBimRevisionBimComparisonState(targetId, TESLA.UpdateBimRevisionRequest.BimComparisonStateEnum.Error, this.errorCode);
}

child process는 throw 시점에 v8가 unhandled async rejection 트레이스를 stderr로 흘리고, 부모는 그 stderr를 라인 단위로 logger.error로 기록한다:

applications/agents/packages/base/src/manager/child-process.manager.ts:138-141typescript
this.process.stderr?.on('data', (data: Buffer) => {
    const text = data.toString();
    logger.error(`ChildProcessManager::setupEventHandlers | Child process stderr: ${text}`);
});

forge-utils의 ForgeClient는 axios 404 응답을 ForgeUtilsError로 wrap한다. 소스가 vendored (node_modules/@cupixapps/forge-utils/...)라서 로컬 체크아웃은 없지만, Datadog stderr 라인이 stack frame을 그대로 보존한다:

from Datadog stderr capture: @cupixapps/forge-utils/server-utils/forge-client.jstext
ForgeUtilsError: Resource not found: file/urn%3Aadsk.fluent%3Afs.file%3Aautodesk-360-translation-storage-prod%2Fw3cEko2vSNuSmggy9xLMHQ%2F21%2Foutput%2F0%2FAECModelData.json
    at ForgeClient.handleApiError (forge-client.js:90:31)
    at ModelDataClient.getBuffer    (forge-client.js:140:25)
    at async ModelDataClient.getData (model-data.js:144:24)
    at async Asset.get               (block.js:18:32)
    at async BIMSVF2Manifest.AEC     (svf2-manifest.js:90:25)
    at async ForgeExtractor.loadAEC  (forge-extractor.js:94:25)
    at async ForgeExtractor.extract  (forge-extractor.js:65:25)
    at async Object.extract          (app.js:78:9)
    at async BimCompareProcess.execute (bim-compare.process.cjs:6459:22)
  errorCode: 'API_NOT_FOUND'

기대 동작과 실제 동작:

  • 기대: child process가 ForgeUtilsError를 catch하면 그 결과를 ForgeAgent.Result.Error / ErrorMessage로 IPC 응답에 담아 부모에 돌려주고, 부모는 ForgeErrorMapper.mapForgeErrorToRevisionError로 revision error code에 매핑하여 단일 logger.warn 또는 logger.error로 기록 (bim-revision-service.ts:663-672).
  • 실제: child 측 ForgeAgent.extract 호출이 throw로 끝나면서 stderr로 트레이스가 흘러 부모의 ChildProcessManager가 stack frame마다 별개의 logger.error로 출력. fingerprint 생성기는 stack frame 1줄 단위로 각 라인을 본문에 잡아 20개의 별개 클러스터를 만든다.

Log Evidence#

Datadog 쿼리 (재현용):

text
service:cupixworks-any-bimrevision-agent status:error @environment:production "ChildProcessManager::setupEventHandlers"

전체 시간 윈도우: 2026-06-24T04:43:00Z ~ 2026-06-24T04:48:00Z (= 13:43 ~ 13:48 KST). 클러스터 파일의 ## Datadog URL이 동일 윈도우를 가리킴.

비즈니스 컨텍스트 로그 (BimRevisionService::REVISE-BEGIN):

json
{
  "bim_id": 18044,
  "src_revision_id": 24593,
  "src_revision_name": "V24",
  "prev_revision_id": 24273,
  "prev_revision_name": "V23",
  "agent_version": "10.98.0",
  "si_trace_id": "676699f8-68e5-40ed-867a-4d3ee79f29a9",
  "cp_elements_count": 0,
  "src_forge_urn": "dXJuOmFkc2sud2lwcHJvZDpmcy5maWxlOnZmLnczY0VrbzJ2U051U21nZ3k5eExNSFE_dmVyc2lvbj0yMQ==",
  "prev_forge_urn": "dXJuOmFkc2sud2lwcHJvZDpmcy5maWxlOnZmLnczY0VrbzJ2U051U21nZ3k5eExNSFE_dmVyc2lvbj0yMA=="
}

src_forge_urn Base64 decode = urn:adsk.wipprod:fs.file:vf.w3cEko2vSNuSmggy9xLMHQ?version=21 — 실패한 stderr의 URL path w3cEko2vSNuSmggy9xLMHQ/21/output/0/AECModelData.json와 정확히 일치. 즉 source revision V24의 Forge translation derivative가 누락되었거나 access가 만료된 상태.

핵심 stderr 라인 (cluster representative):

text
2026-06-24 13:44:08  error  ChildProcessManager::setupEventHandlers | Child process stderr: ForgeUtilsError: Resource not found: file/urn%3Aadsk.fluent%3Afs.file%3Aautodesk-360-translation-storage-prod%2Fw3cEko2vSNuSmggy9xLMHQ%2F21%2Foutput%2F0%2FAECModelData.json?acmsession=dXJuOmFkc2sud2lwcHJvZDpmcy5maWxlOnZmLnczY0VrbzJ2U051U21nZ3k5eExNSFE_dmVyc2lvbj0yMQ
2026-06-24 13:44:08  error  ChildProcessManager::setupEventHandlers | Child process stderr:   errorCode: 'API_NOT_FOUND'
2026-06-24 13:44:10  error  (same trace dumped a second time when CompareExtractor.compare → calculateOrigin → BIMSVF2Manifest.AEC reaches the same Asset.get path)

부모 측 catch 로그는 동일 트레이스 윈도우에는 검색되지 않았다(BimRevisionService::run | error 키워드로 now-2h 조회 결과 없음 — uncertain, 다른 인덱스/메타데이터에 있을 수 있음). 이는 부모 catch가 동작하지 않았다는 뜻이 아니라, stderr 폭증이 fingerprint를 가져가서 부모 단일 catch 로그가 같은 검색 필터에 잡히지 않았을 가능성이 크다.

Status board 출력 (bun run cli/incident-board.ts for-cluster 86dc1f15-...)으로 확인된 형제 클러스터 19개: 1ed7609e..., 9597edbe..., 058cbc7f..., 2ecd63f7..., bc3e9330..., 29338981..., 3c70958c..., 2696d075..., b5b1dda5..., 08a54588..., ffec5521..., da62b8da..., 3e4a8245..., 7dab277d..., 57635135..., 41c055b3..., 76ddf31d..., aa868d47..., f16ad851... — 모두 동일한 stderr 트레이스의 다른 스택 라인.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 Autodesk Forge가 source URN(version 21)의 AECModelData.json derivative를 404로 반환했고, child process가 이를 stderr로 출력하면서 stack frame당 하나의 클러스터가 생성됨 (단일 비즈니스 실패 + 로그 폭증) stderr 본문에 errorCode: 'API_NOT_FOUND', URL이 autodesk-360-translation-storage-prod/.../21/output/0/AECModelData.json, REVISE-BEGIN의 src_forge_urn Base64 decode 결과가 version 21로 일치, status board가 동일 시각 20개 형제 클러스터 검출 Confirmed
H2 Forge 인증/토큰 만료(401/403)로 인한 일시적 실패 stderr가 명시적으로 errorCode: 'API_NOT_FOUND'이며 path가 Resource not found 메시지. 인증 실패였다면 UNAUTHORIZED/FORBIDDEN이었을 것 Rejected
H3 child process 자체 crash (OOM/segfault)로 인한 일반 실패 Child process closed/exited 로그가 등장 종료 사유가 비정상 시그널이 아니라 throw → 자연 종료. 부모의 BimCompareManager.execute catch가 정상 동작 (코드상 setErrorCode 호출로 revision state Error 기록 경로 존재) Rejected
H4 source와 previous URN이 동일하여 validateForgeUrnDifference가 실패 REVISE-BEGIN 로그에서 src_forge_urn(v21)과 prev_forge_urn(v20)이 명백히 다름. validateForgeUrnDifference 통과 후 runBimCompare가 실행되었다는 후속 로그 존재 Rejected
H5 Status board가 표시한 service degradation은 외부 의존성 광역 장애 (Autodesk Forge 자체) 동일 자산에 대한 반복 404 다른 BIM/URN의 비교 작업에서는 같은 메시지가 추가로 등장하지 않음 — 단일 자산 미생성. AWS Service Health 등 외부 status 페이지는 본 RCA에서 조회하지 않음 — uncertain, 광역 장애는 가능성 낮음 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  1. 운영 조치 — 코드 변경 불필요. bim_revision_id=24593 (bim_id=18044, V24) revision의 Forge translation을 재요청. Autodesk Forge Model Derivative API에서 urn:adsk.wipprod:fs.file:vf.w3cEko2vSNuSmggy9xLMHQ?version=21에 대해 SVF2 translation을 POST → 완료 후 BIM revision job을 retry. 사용자에게는 일시적 비교 실패로 안내 가능.
  2. 확인 사항: 같은 facility의 다른 BIM revision에서 동일 w3cEko2vSNuSmggy9xLMHQ asset에 대한 404가 반복되지 않는지 24시간 모니터링. 반복된다면 Autodesk 측에 translation manifest 영구 손실 여부 문의.

단기 개선 (1주 이내)#

  1. child-process.manager.ts:138-141 — stderr 로그 레벨 다운그레이드. child process의 stderr는 본질적으로 "child가 unhandled rejection을 dump하기 전에 흘리는 트레이스"이거나 dependency의 deprecation 경고(AWS SDK v2 maintenance notice가 stderr로 흐르고 error로 기록되는 현상이 같은 윈도우에서 5건 확인됨)다. 이 stream을 logger.warn 또는 trace-aware한 buffered 로깅으로 바꾸어 stack frame당 별개 error 로그 생성을 차단한다. 비즈니스 실패는 이미 child의 정상 IPC 응답(ForgeAgent.Result.Error) 또는 BimCompareManager.execute catch가 부모에서 한 번 logger.error로 기록하므로 stderr를 별도 error로 올릴 필요가 없다.
  2. child process 측 catch. bim-compare.process.ts:67-115execute catch에서 ForgeUtilsError (또는 errorCode API_NOT_FOUND)를 잡아 throw 대신 ForgeAgent.ResultError/ErrorMessage 필드로 채워 반환하는 분기를 추가. 그러면 부모의 runBimCompare가 이미 가지고 있는 ForgeErrorMapper 경로(bim-revision-service.ts:663-672)를 타게 되어 revision error code가 의미 있는 값(BimRevisionExtractorNotFound 등 신규 매핑)으로 들어가고, child stderr 폭증 자체가 사라진다.
  3. 에러 코드 매핑 보강. ForgeErrorMapper.mapForgeErrorToRevisionError(forge-error-mapper.ts:33-39)에 API_NOT_FOUND 케이스를 추가해 BimRevisionForgeAssetMissing 같은 명시적 코드를 부여, 사용자 노출 메시지가 일반적 UNKNOWN이 아닌 "원본 모델 derivative 미존재"로 안내되게 한다.

장기 개선 (재발 방지)#

  1. Forge derivative pre-check. revision queue를 처리하기 전에 cupixApi.bim.get / Forge Manifest API로 source/previous URN의 derivative status를 확인하고, 미완료면 Comparing 상태로 두고 retry queue에 넣는 게이트 추가. 404 후 Error state로 끝내고 사용자가 수동 트리거해야 하는 현재 흐름을 줄인다.
  2. Fingerprint 정책. error-sweeper classifier에서 동일 child process stderr 트레이스가 다중 클러스터로 쪼개지지 않도록, stderr 본문이 "stack frame line"인 패턴(at async ...:NN:NN, errorCode: ...)을 같은 fingerprint로 병합하는 룰 추가. 이번 incident에서 본 20개 클러스터는 모두 1건의 동일 사건이다.

Monitoring#

다음 Datadog 쿼리는 release dashboard timeseries widget에 그대로 사용 가능하며, writing-datadog-monitoring-queries 가이드라인의 monitor-only 문법(| stats, count by(...))을 회피한다.

ForgeUtilsError 404 발생 빈도 (전체):

text
service:cupixworks-any-bimrevision-agent status:error "ForgeUtilsError" "API_NOT_FOUND"

해당 서비스의 stderr-bridge 로그 폭증 모니터링 (child process stderr이 logger.error로 흐르는 양):

text
service:cupixworks-any-bimrevision-agent status:error "ChildProcessManager::setupEventHandlers" "Child process stderr"

BIM revision Error state 갱신 (비즈니스 실패의 ground truth):

text
service:cupixworks-api "UpdateBimRevisionRequest" "bim_comparison_state" "Error"

알림 임계값 제안 (rate 기반 monitor를 별도로 만들 때만 사용 — 위 widget 쿼리에는 포함하지 말 것):

  • ForgeUtilsError "API_NOT_FOUND" count > 5/10분: warn — 단일 BIM 자산 미존재가 반복.
  • Child process stderr count > 30/10분: warn — stderr→error 로그 폭증, 다른 비즈니스 실패가 가려질 위험.

Risk Assessment#

  • Risk level: low (사용자 가시 영향은 단일 BIM revision 1건; 다른 작업/서비스로 전파 없음)
  • 예상 복잡도: standard (운영 조치만으로 즉시 회복 가능. 단기 개선 항목(child stderr 로깅 다운그레이드 + child catch에서 IPC 응답으로 결과 반환)은 cupixworks/applications/agents 내부 PR 1~2개로 마무리 가능. 장기 개선의 Forge derivative pre-check는 별도 epic)