ChildProcessManager stderr-as-error 매핑 — 경고를 에러로 분류
RCA: ChildProcessManager child process stderr — BIMSVF2Manifest.AEC Forge 404
Overview#
What Happened#
2026-06-24 13:44 KST 무렵 cupixworks-any-bimrevision-agent 서비스에서 자식 프로세스 stderr 한 줄당 한 건씩 error 레벨 로그가 다수 발생했다. 메시지 본문은 forge-agents가 Autodesk Forge의 AECModelData.json 리소스를 404(API_NOT_FOUND)로 응답받으면서 출력한 ForgeUtilsError 스택 트레이스 한 줄씩이다. 같은 클러스터 그룹(svc:cupixworks-any-bimrevision-agent::unknown)으로 20개 클러스터가 동시에 묶였고, 약 3분 뒤 자동 resolved 되었다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | ForgeUtilsError |
| exception.message | Resource not found: file/urn%3Aadsk.fluent%3Afs.file%3Aautodesk-360-translation-storage-prod%2Fw3cEko2vSNuSmggy9xLMHQ%2F21%2Foutput%2F0%2FAECModelData.json |
| top_frame | node_modules/@cupixapps/forge-utils/convert-utils/svf2/svf2-manifest.js:90 |
| runtime | Node.js child process (fork) under cupixworks-any-bimrevision-agent |
| env | production, region us-west-2, tenant cupix |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| gad / bim-revision-agent | 3 | 로그 노이즈만 발생. BIM compare 결과는 정상적으로 후속 단계(uploadDataFiles)로 진행됨. 사용자 영향 없음. |
Timeline#
- 2026-06-24 13:44:08 KST —
BimRevisionService::runBimCompare가 자식 프로세스에 BIM 비교 요청을 위임. 자식 프로세스 내부의forge-agents가 Autodesk ForgeAECModelData.jsonURL에 GET 요청. - 2026-06-24 13:44:10 KST — Forge가 404 응답.
forge-agents내부에서 catch되어ForgeUtilsError스택 트레이스가 자식 프로세스 stderr로 출력됨. - 2026-06-24 13:44:10 KST —
ChildProcessManager의stderr.on('data')핸들러가 stderr 청크 한 줄당logger.error호출. 클러스터 fingerprint가 stack frame 행마다 다르게 만들어져 20개의svc:*::unknown클러스터가 동시 발생. - 2026-06-24 13:44:10 KST —
BimRevisionService::runBimCompare | end정상 로깅,BimRevisionService::uploadDataFiles | begin으로 진행. forge-agents가 404를 무시 가능한(handle-internally) 케이스로 분류한 결과. - 2026-06-24 13:47:03 KST — 같은 자식 프로세스에서 AWS SDK v2 deprecation NOTE가 stderr로 추가 출력. 동일 메커니즘으로 error 로그 발생. 그 후 클러스터 인시던트 auto-resolved.
Error Log#
ChildProcessManager::setupEventHandlers | Child process stderr: at async BIMSVF2Manifest.AEC (/tmp/agent/dist/node_modules/.pnpm/@cupixapps+forge-utils@10.98.0_axios@1.16.1_commander@3.0.2_fflate@0.8.3_rxjs@7.8.2_three@0.170.0_ws@8.21.0/node_modules/@cupixapps/forge-utils/convert-utils/svf2/svf2-manifest.js:90:25)
전체 stderr 묶음(원문, Datadog에서 조립):
ForgeUtilsError: Resource not found: file/urn%3Aadsk.fluent%3Afs.file%3Aautodesk-360-translation-storage-prod%2Fw3cEko2vSNuSmggy9xLMHQ%2F21%2Foutput%2F0%2FAECModelData.json?acmsession=dXJuOmFkc2sud2lwcHJvZDpmcy5maWxlOnZmLnczY0VrbzJ2U051U21nZ3k5eExNSFE_dmVyc2lvbj0yMQ
at ForgeClient.handleApiError (.../forge-utils/server-utils/forge-client.js:90:31)
at ModelDataClient.getBuffer (.../forge-utils/server-utils/forge-client.js:140:25)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async ModelDataClient.getData (.../forge-utils/server-utils/model-data.js:144:24)
at async Asset.get (.../forge-utils/convert-utils/core-utils/block.js:18:32)
at async BIMSVF2Manifest.AEC (.../forge-utils/convert-utils/svf2/svf2-manifest.js:90:25)
at async GridAlignment.computeGridCorrection (.../forge-agents/extractor/compare-extractor/grid-alignment.js:25:27)
at async Object.calculateOrigin (.../forge-agents/extractor/compare-extractor/compare-utils.js:76:36)
at async CompareExtractor.compare (.../forge-agents/extractor/compare-extractor/compare-extractor.js:62:43)
at async extractComparison (.../forge-agents/app.js:133:20) {
errorCode: 'API_NOT_FOUND'
}
Impact#
- Service:
cupixworks-any-bimrevision-agent - Team: gad
- 발생 횟수: 3
- 최초 발생: 2026-06-24 13:44 KST
- 최근 발생: 2026-06-24 13:44 KST
이 클러스터(08a54588-19a5-47be-b24c-cacd7938758b)는 BIMSVF2Manifest.AEC stack frame 한 줄을 fingerprint로 가진다. 같은 incident에 같은 stderr 페이로드를 frame 단위로 묶은 19개 동반 클러스터(1ed7609e-..., 9597edbe-... 등)가 함께 묶여 있다. BIM revision 비교 자체는 정상 완료되어 후속 단계로 진행되었으므로 사용자 워크플로 차단은 발생하지 않았다.
Root Cause Summary#
ChildProcessManager가 자식 프로세스의 stderr 청크를 모두 logger.error로 출력하는 구조 때문에, 자식 안에서 내부적으로 catch되어 무시 가능한 Forge 404 스택 트레이스가 error-sweeper에서 production error로 분류되었다. 실제 BIM 비교 결과는 forge-agents가 Result.Error / Result.ErrorMessage 필드로 부모 프로세스에 IPC 전달하고, BimRevisionService::runBimCompare의 ForgeErrorMapper.isIgnorableError 분기가 처리한다. 즉, 사용자 영향은 없는 로그 노이즈이고, root cause는 stderr → error 로깅 매핑(설계상 모든 stderr를 error로 간주)에 있다.
Technical Analysis#
Code Path#
Entry point: BimRevisionService::runBimCompare invokes the child process.
const bimCompareResults = await this.bimCompareManager.execute(params);
const newBimOrigin = bimCompareResults?.Origin;
const report = bimCompareResults?.Compare;
// ...
const forgeErrorCode = bimCompareResults?.Error;
const forgeErrorMessage = bimCompareResults?.ErrorMessage;
if (forgeErrorCode) logger.warn('CPBimRevision::validateBimComparerResult | forgeErrorCode: %s', forgeErrorCode?.toString());
if (forgeErrorMessage) logger.warn('CPBimRevision::validateBimComparerResult | forgeErrorMessage: %s', forgeErrorMessage?.toString());
if (forgeErrorCode) {
if (ForgeErrorMapper.isIgnorableError(forgeErrorCode)) {
logger.warn('BimRevisionService::runBimCompare | Ignorable Forge error detected - forgeErrorCode: %s, message: %s', forgeErrorCode, forgeErrorMessage);
} else {
// map and throw
}
}
자식 프로세스(BimCompareProcess)는 forge-agents의 extract를 호출한다. 내부에서 발생한 Forge 404는 forge-agents가 잡아 Result.Error/Result.ErrorMessage로 직렬화하여 IPC로 돌려준다. 그 과정에서 stack trace가 자식의 stderr에 출력되는 것이 본 이슈의 원인이다.
private async execute(params: BimComparerParams): Promise<ForgeAgent.Result> {
try {
// ...
const result: ForgeAgent.Result = await ForgeAgent.extract(params.apiConfig, params.urn, {
region: params.region,
extractRoom: false,
extractMeta: false,
extractCompare: {
urn: params.previousUrn,
region: params.previousRegion,
query: params.query
}
});
// ... logs result.hasError / result.ErrorMessage
return result;
} catch (error) {
// ...
}
}
Failure point (로그 분류 기준): ChildProcessManager stderr 핸들러가 forge-agents의 내부 로깅을 무조건 logger.error로 흘려보낸다.
this.process.stderr?.on('data', (data: Buffer) => {
const text = data.toString();
logger.error(`ChildProcessManager::setupEventHandlers | Child process stderr: ${text}`);
});
기대 동작: stderr 출력은 일반적으로 fatal한 것이 아니므로 warn 또는 info로 격하하거나, "process did not exit with non-zero code" 인 경우 다운그레이드한다.
실제 동작: stderr의 모든 청크가 line 단위로 잘려 각각 별개의 error 로그가 되고, error-sweeper의 fingerprint 분류기는 stack frame 라인마다 fingerprint를 다르게 계산하므로 단일 사건이 20개 클러스터로 폭증한다.
같은 메커니즘으로 노이즈를 만드는 다른 사례가 같은 분석 윈도우에 관측된다:
ChildProcessManager::setupEventHandlers | Child process stderr: (node:45) NOTE: The AWS SDK for JavaScript (v2) will enter maintenance mode
ChildProcessManager::setupEventHandlers | Child process stderr: on September 8, 2024 and reach end-of-support on September 8, 2025.
ChildProcessManager::setupEventHandlers | Child process stderr: Please migrate your code to use AWS SDK for JavaScript (v3).
위는 AWS SDK v2 deprecation notice인데, deprecation NOTE는 본질적으로 error가 아니다. 그럼에도 동일하게 logger.error로 기록된다.
Log Evidence#
Datadog query (재현용):
service:cupixworks-any-bimrevision-agent status:error "ChildProcessManager::setupEventHandlers"
원문 stderr 흐름 (2026-06-24 13:44:10 KST, Datadog):
13:44:10 info BimRevisionService::runBimCompare | report.modified.length: 0, report.removed.length: 0, report.exist.length: 0
13:44:10 info BimRevisionService::runBimCompare | report.sourceGlobalOffset=[391.60...], targetGlobalOffset=[391.60...]
13:44:10 info BimRevisionService::runBimCompare | view comparison: prevViewId=5, curViewId=5, curViewName=Default
13:44:10 error ChildProcessManager::setupEventHandlers | Child process stderr: ForgeUtilsError: Resource not found: file/urn%3Aadsk.fluent%3Afs.file%3Aautodesk-360-translation-storage-prod%2F.../AECModelData.json?...
13:44:10 error ChildProcessManager::setupEventHandlers | Child process stderr: at ForgeClient.handleApiError (.../forge-client.js:90:31)
13:44:10 error ChildProcessManager::setupEventHandlers | Child process stderr: at ModelDataClient.getBuffer (.../forge-client.js:140:25)
13:44:10 error ChildProcessManager::setupEventHandlers | Child process stderr: at async BIMSVF2Manifest.AEC (.../svf2-manifest.js:90:25)
13:44:10 error ChildProcessManager::setupEventHandlers | Child process stderr: at async GridAlignment.computeGridCorrection (.../grid-alignment.js:25:27)
13:44:10 error ChildProcessManager::setupEventHandlers | Child process stderr: at async extractComparison (.../forge-agents/app.js:133:20) {
13:44:10 error ChildProcessManager::setupEventHandlers | Child process stderr: errorCode: 'API_NOT_FOUND'
13:44:10 error ChildProcessManager::setupEventHandlers | Child process stderr: }
13:44:10 info CPBimRevision::validateBimComparerResult | newBimOrigin: 141.31..., currentOrigin: 141.31...
13:44:10 info CPBimRevision::validateBimComparerResult | modified: 0, removed: 0, exist: 0
13:44:10 info BimRevisionService::runBimCompare | end
13:44:10 info BimRevisionService::uploadDataFiles | begin
핵심: stderr error 로그 직후에도 BimRevisionService::runBimCompare | end 와 uploadDataFiles | begin 가 그대로 흘러간다. forge-agents 내부에서 처리된 비치명적 stderr 출력임이 증명된다.
추가로 status board 결과(scope: svc:cupixworks-any-bimrevision-agent::unknown)는 같은 시점에 동일 stderr 페이로드의 frame 단위로 19개 동반 클러스터가 함께 묶여 있음을 보여준다. 인시던트 ID 2026-06-24-svc-cupixworks-any-bimrevision-agent--unknown-1은 status resolved.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | ChildProcessManager.stderr 핸들러가 무조건 logger.error로 출력하여, 자식 안에서 catch된 stack trace까지 production error로 분류된다 |
packages/base/src/manager/child-process.manager.ts:138-141에서 stderr 청크 전체를 logger.error로 기록. 같은 메커니즘으로 AWS SDK v2 deprecation NOTE도 error 로그로 분류됨(2026-06-24 13:46:56, 13:47:03 KST). stderr 직후 runBimCompare | end가 정상 로깅됨 |
— | Confirmed |
| H2 | Autodesk Forge 외부 의존성 장애 (404 발생 자체가 root cause) | ForgeUtilsError: Resource not found ... AECModelData.json 404 로그 발생 |
forge-agents가 결과 객체의 Error/ErrorMessage 필드로 비교 결과를 정상 반환했고, `BimRevisionService::runBimCompare |
end로 끝나고uploadDataFiles |
| H3 | 자식 프로세스가 비정상 종료(exit code != 0)되어 BIM revision job 자체가 실패 | — | ChildProcessManager의 close/exit 핸들러(child-process.manager.ts:107-127)는 별도의 Child process closed/Child process exited 로그를 남기는데, 분석 윈도우에는 해당 로그가 없음. runBimCompare | end 도달 사실로 child가 IPC 응답을 정상 반환했음이 확인됨 |
Rejected |
| H4 | error-sweeper fingerprint 분류기가 같은 사건을 frame 단위로 과도하게 쪼개 한 사건이 다수 클러스터로 폭증 | status board에서 동일 사건이 20개 cluster로 묶여 svc:cupixworks-any-bimrevision-agent::unknown 인시던트를 형성 |
본 클러스터의 root cause는 아니지만, 영향 폭(20개 cluster)을 만든 보조 요인 | Confirmed (보조 요인) |
Fix Recommendation#
즉시 조치 (Critical)#
없음. 사용자 워크플로에 영향 없는 stderr 노이즈이며, 실제 비교 결과는 forge-agents가 IPC로 정상 반환했다. 인시던트는 이미 auto-resolved.
단기 개선 (1주 이내)#
packages/base/src/manager/child-process.manager.ts:138-141의 stderr 핸들러를 다음 방향으로 조정한다.- 자식의 stderr 출력은 기본적으로
warn레벨로 격하한다. error 분류는 child의exit code != 0또는process.on('error')시그널에 한정한다. (node:NN) NOTE:/(node:NN) DeprecationWarning:/(node:NN) ExperimentalWarning:같은 Node.js 내장 경고 접두사는info로 다운그레이드.Stack/ForgeUtilsError: Resource not found같이 forge-agents 가 IPC 결과로 정상 반환하는 케이스의 stderr는 child측에서 별도 채널(예: structuredlog_errorIPC)로 전달하도록 forge-agents에 요청하거나, 부모에서 buffer 후 child가 정상 exit하면 디스카드한다.
- 자식의 stderr 출력은 기본적으로
BimRevisionService::runBimCompare(bim-revision-service.ts:660-672)에서forgeErrorCode === 'API_NOT_FOUND'인 경우ForgeErrorMapper.ignorableErrorCodes에 추가할지 검토. 현재는ROOM_EXT_ROOM_MAKER_IS_NOT_DEFINED만 ignorable. AEC manifest 부재가 비교 자체를 막지 않는다면 ignorable로 분류하여 명시적으로warn처리한다.
장기 개선 (재발 방지)#
- 자식 프로세스 로그 정책을 명문화: stderr는 "프로세스가 죽었거나 IPC가 깨졌을 때만 error", 그 외에는 warn/info. 현재는 모든 자식 stderr가 error로 흘러 인시던트 검출이 흐려진다.
forge-agents에 stderr 직접 출력 대신 structured logger를 주입하거나, child가 결과 객체에 담아 IPC로만 보고하는 패턴을 강제. third-party SDK가 stderr로 deprecation notice를 출력하는 경우도 같은 방식으로 흡수.- error-sweeper fingerprint 분류기에서 stack frame 라인 단위 폭증을 방지하기 위해, "동일 메시지 prefix" 그룹화 룰(예:
ChildProcessManager::setupEventHandlers | Child process stderr:prefix는 한 fingerprint로 묶기)을 검토.
Monitoring#
신규 Datadog 모니터링 쿼리:
sum:logs.hits{service:cupixworks-any-bimrevision-agent,status:error,@message:"ChildProcessManager::setupEventHandlers | Child process stderr"}.as_count()
stderr 폭증을 감지하는 별도 트리거. 단기 개선 후 이 카운트가 의미 있는 수준으로 줄어드는지 확인한다.
forge 404 실제 발생률(IPC 결과 기준)을 트래킹하려면:
sum:logs.hits{service:cupixworks-any-bimrevision-agent,status:warn,@message:"forgeErrorCode"}.as_count()
BimRevisionService::runBimCompare 가 처리한 forge 에러 코드 빈도. 404가 만성적이라면 Autodesk Forge integration 측 대응을 검토.
Risk Assessment#
- Risk level: low
- 예상 복잡도: standard
사용자 영향 없는 로그 노이즈가 production error로 분류되는 문제이므로 즉시 코드 fix는 필요하지 않다. 그러나 같은 메커니즘이 향후 실제 장애 시그널을 덮어버릴 수 있으므로 단기 개선(자식 stderr → warn 격하, ignorable forge error 분류 확장)을 권장한다.