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#
- 2026-06-24 13:44:03 KST —
AwsS3Manager초기화, BIM revision 메시지(id 24593) 처리 시작 - 2026-06-24 13:44:05 KST — child process spawn, AWS SDK v2 maintenance 경고가 stderr로 출력되어
error레벨 로그 5건 생성 - 2026-06-24 13:44:07 KST —
BimRevisionService::REVISE-BEGIN(src_forge_urnversion 21,prev_forge_urnversion 20),runBimCompare호출 - 2026-06-24 13:44:08 KST —
ForgeExtractor.loadAEC→BIMSVF2Manifest.AEC→ModelDataClient.getData가 Forge에서 version 21의AECModelData.json404 응답,ForgeUtilsError: Resource not foundthrow (first_seen) - 2026-06-24 13:44:10 KST —
CompareExtractor.compare경로에서 동일 asset 재호출, 같은 404로 두 번째 stderr 덤프 (last_seen) - 2026-06-24 13:44:10 KST — child process exit,
BimCompareManager::executecatch →BimRevisionExtractorExecute에러 코드 설정 →BimRevisionService::runcatch → revision state를Error로 업데이트 - 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#
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::runBimCompare는 cpBimRevision.forgeUrn(version 21)을 그대로 child process로 전달하고, @cupixapps/forge-utils의 ModelDataClient.getBuffer → ForgeClient.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:603—runBimCompare - Parent → child IPC:
bim-compare.manager.ts:65—childProcessManager.execute<ForgeAgent.Result>('execute', params) - Child entry:
bim-compare.process.ts:67—BimCompareProcess.execute→ForgeAgent.extract(...) - Failure call site (vendored):
@cupixapps/forge-utils/server-utils/forge-client.js:90—ForgeClient.handleApiError - stderr→logger bridge (where the error log fingerprint is generated):
child-process.manager.ts:138-141
부모는 정상적으로 catch한다 (bim-revision-service.ts:188-191):
} 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로 기록한다:
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을 그대로 보존한다:
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 쿼리 (재현용):
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):
{
"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):
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)#
- 운영 조치 — 코드 변경 불필요.
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. 사용자에게는 일시적 비교 실패로 안내 가능. - 확인 사항: 같은 facility의 다른 BIM revision에서 동일
w3cEko2vSNuSmggy9xLMHQasset에 대한 404가 반복되지 않는지 24시간 모니터링. 반복된다면 Autodesk 측에 translation manifest 영구 손실 여부 문의.
단기 개선 (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.executecatch가 부모에서 한 번logger.error로 기록하므로 stderr를 별도 error로 올릴 필요가 없다.- child process 측 catch.
bim-compare.process.ts:67-115의executecatch에서ForgeUtilsError(또는 errorCodeAPI_NOT_FOUND)를 잡아 throw 대신ForgeAgent.Result의Error/ErrorMessage필드로 채워 반환하는 분기를 추가. 그러면 부모의runBimCompare가 이미 가지고 있는ForgeErrorMapper경로(bim-revision-service.ts:663-672)를 타게 되어 revision error code가 의미 있는 값(BimRevisionExtractorNotFound등 신규 매핑)으로 들어가고, child stderr 폭증 자체가 사라진다. - 에러 코드 매핑 보강.
ForgeErrorMapper.mapForgeErrorToRevisionError(forge-error-mapper.ts:33-39)에API_NOT_FOUND케이스를 추가해BimRevisionForgeAssetMissing같은 명시적 코드를 부여, 사용자 노출 메시지가 일반적UNKNOWN이 아닌 "원본 모델 derivative 미존재"로 안내되게 한다.
장기 개선 (재발 방지)#
- Forge derivative pre-check. revision queue를 처리하기 전에
cupixApi.bim.get/ Forge Manifest API로 source/previous URN의 derivative status를 확인하고, 미완료면Comparing상태로 두고 retry queue에 넣는 게이트 추가. 404 후Errorstate로 끝내고 사용자가 수동 트리거해야 하는 현재 흐름을 줄인다. - 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 발생 빈도 (전체):
service:cupixworks-any-bimrevision-agent status:error "ForgeUtilsError" "API_NOT_FOUND"
해당 서비스의 stderr-bridge 로그 폭증 모니터링 (child process stderr이 logger.error로 흐르는 양):
service:cupixworks-any-bimrevision-agent status:error "ChildProcessManager::setupEventHandlers" "Child process stderr"
BIM revision Error state 갱신 (비즈니스 실패의 ground truth):
service:cupixworks-api "UpdateBimRevisionRequest" "bim_comparison_state" "Error"
알림 임계값 제안 (rate 기반 monitor를 별도로 만들 때만 사용 — 위 widget 쿼리에는 포함하지 말 것):
ForgeUtilsError "API_NOT_FOUND"count > 5/10분: warn — 단일 BIM 자산 미존재가 반복.Child process stderrcount > 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)