ES /docs

ChildProcessManager logs all stderr as errors — AWS SDK v2 warnings

RCA: ChildProcessManager::setupEventHandlers | Child process stderr — ForgeClient.handleApiError

Overview#

What Happened#

2026-06-24 13:44 KST에 cupixworks-any-bimrevision-agent 가 BIM revision 비교 작업(bim_id=18044, src_revision_id=24593)을 처리하는 동안 child process(BimCompareProcess) 의 stderr 로 Autodesk Forge AECModelData.json 404 응답 스택트레이스가 그대로 출력되었다. parent 의 ChildProcessManager.setupEventHandlers 는 stderr 전체를 logger.error(...) 로 라인 단위로 받아쓰기 때문에 한 번의 Forge API 404가 수십 줄의 error 레벨 로그로 분해되어 노이즈로 잡혔다. BIM revision 잡 자체는 BimRevisionService::REVISE-END 에서 "status":"success" 로 종료되었다 — 사용자 영향 없음.

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
errorCode API_NOT_FOUND
top_frame @cupixapps/forge-utils/server-utils/forge-client.js:90:31 (ForgeClient.handleApiError)
runtime Node.js child process (forked from BimCompareManager)
package @cupixapps/forge-utils@10.98.0, @cupixapps/forge-agents@10.98.0
env production, us-west-2

Affected Teams#

Team / Domain Error Count Impact
gad (BIM revision agent) 3 (이 cluster) 사용자 영향 없음 — 잡은 정상 종료 (status:success). 로그 노이즈만 발생

Timeline#

  1. 2026-06-24 13:44:07 KSTBimRevisionService::REVISE-BEGIN | bim_id=18044, src_revision_id=24593 (V24), prev_revision_id=24273 (V23) — Forge URN urn:adsk.wipprod:fs.file:vf.w3cEko2vSNuSmggy9xLMHQ (version 20 ↔ 21) 비교 시작
  2. 2026-06-24 13:44:08 KST — child process stderr 에 ForgeUtilsError: Resource not found ... /21/output/0/AECModelData.json 첫 발생 (first_seen)
  3. 2026-06-24 13:44:10 KST — version 20 / version 21 양쪽에 대해 동일한 404 한 번 더 발생 (last_seen, 총 3건)
  4. 2026-06-24 13:46:57 KST — 별개의 bim_id=20016 revision 비교 시작 (관련 없는 후속 잡)
  5. 2026-06-24 13:48:18 KSTBimRevisionService::REVISE-END | bim_id=18044 ... "status":"success", "elapsed_ms":253285 — 404 발생에도 잡은 success 종료

Error Log#

Datadog Logs

text
ChildProcessManager::setupEventHandlers | Child process stderr:     at ForgeClient.handleApiError (/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/server-utils/forge-client.js:90:31)

Impact#

  • Service: cupixworks-any-bimrevision-agent
  • Team: gad
  • 발생 횟수: 3
  • 최초 발생: 2026-06-24 13:44 KST
  • 최근 발생: 2026-06-24 13:44 KST
  • 사용자 영향: 없음. BimRevisionService::REVISE-ENDstatus:"success" 로 종료됨. Compare 결과 modified=0, removed=0, exist=0 으로 정상 처리.

Root Cause Summary#

근본 원인은 child process 의 stderr 처리 방식이다. BimCompareManager 는 BIM 비교 작업을 위해 ChildProcessManager 로 child process 를 fork 하고, 부모는 child_process.manager.ts:138-141 에서 process.stderr.on('data') 에 들어오는 모든 buffer chunk 를 logger.error(...) 로 흘려보낸다. 그런데 child process 안에서 동작하는 외부 패키지 @cupixapps/forge-utils@10.98.0ForgeClient.handleApiError (forge-client.js:90) 가 Autodesk Forge Model Derivative API 에서 404 (AECModelData.json not found) 를 받았을 때 에러를 그대로 throw 하면 Node 가 unhandled rejection 또는 catch 후 console 출력 형태로 stderr 에 stack trace 를 newline 단위로 뱉는다. 부모 쪽 stderr.on('data') 는 chunk 단위로 잘려 들어오므로 stack 한 줄(예: at ForgeClient.handleApiError ...) 이 단독 chunk 로 도착해 fingerprint 매칭 시 이 한 줄이 cluster 의 representative error 가 되었다. 한편 child 의 내부 catch 가 Compare 단계에서 404 를 swallow 하기 때문에 BimRevisionService 레벨에서는 잡이 success 로 종료된다. 즉, 이 cluster 는 외부 Forge API 의 404 가 parent 의 stderr passthrough 로 인해 status:error 로 잘못 분류된 “로그 노이즈” 다.

Technical Analysis#

Code Path#

Parent 쪽 stderr forwarding:

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}`);
});

stdioMode: 'pipe' 로 fork 한 경우(BIM revision 은 native C++ 라이브러리 호출 가능성 때문에 pipe 사용) child 가 stderr 에 쓰는 모든 라인이 그대로 logger.error 로 흘러간다. 청크는 줄 단위로 분리되지 않고 OS buffer 단위로 잘려 들어오기 때문에 한 stack trace 가 여러 error 로그로 분할된다.

Child process 내 호출 흐름 (top_frame 인 ForgeClient.handleApiError 까지):

applications/agents/packages/cupix-tesla-bim-revision-agent/src/process/bim-compare.process.ts:67-115typescript
private async execute(params: BimComparerParams): Promise<ForgeAgent.Result> {
    try {
        this.log(`BimCompareProcess::execute | forge-agents version: ${forgeAgentsPkg.version}, spec_version: ${SpecVersion}`);
        this.log('BimCompareProcess::execute | start');

        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
            }
        });
        ...
    } catch (error) {
        ...
        throw error;
    }
}

ForgeAgent.extract 내부에서 prev/current 두 URN 의 SVF2 manifest(AECModelData.json) 를 받아오려고 ForgeClient.getBufferForgeClient.handleApiError 를 호출한다. 404 시 handleApiErrorForgeUtilsError 를 throw 하지만, 호출 체인 어딘가 (compare-extractor.js, grid-alignment.js) 에서 이 에러를 catch 하기 때문에 child process 는 정상 종료되고 BimCompareProcess::executeresult 도 받게 된다. 다만 catch 전에 axios/Node 가 stack trace 를 stderr 로 인쇄하는 시점이 있어 parent 가 그것을 잡아 error 레벨로 기록한다.

Parent의 BimCompareManager 가 에러를 catch 하면 BimRevisionExtractorExecute 라는 errorCode 가 호출자에 전달되어야 하지만, 실제로는 child 가 결과를 정상 반환했기 때문에 setErrorCode 도 호출되지 않는다:

applications/agents/packages/cupix-tesla-bim-revision-agent/src/manager/bim-compare.manager.ts:53-79typescript
async execute(params: BimComparerParams): Promise<ForgeAgent.Result> {
    ...
    try {
        const result = await this.childProcessManager.execute<ForgeAgent.Result>('execute', params);
        logger.debug('BimCompareManager::execute | completed successfully');
        return result;
    } catch (error) {
        logger.error('BimCompareManager::execute | error:', error);
        const errorCode = ErrorCode.Agent.BimRevisionExtractorExecute;
        if (this.setErrorCode) {
            this.setErrorCode(errorCode);
        }
        throw error;
    }
}

Failure point 는 코드의 버그가 아니라 로깅 정책이다: child 의 stderr 가 정상 처리되는 (recoverable) 에러임에도 무조건 logger.error 로 분류된다.

Log Evidence#

Datadog 쿼리:

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

Time window: 2026-06-24T04:30:00Z ~ 2026-06-24T05:00:00Z.

핵심 stderr 라인 — AECModelData.json 404 (version 21):

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
ChildProcessManager::setupEventHandlers | Child process stderr:     at ForgeClient.handleApiError (/tmp/agent/dist/node_modules/.pnpm/@cupixapps+forge-utils@10.98.0_.../forge-client.js:90:31)
ChildProcessManager::setupEventHandlers | Child process stderr:     at ModelDataClient.getBuffer (.../model-data.js:144:24)
ChildProcessManager::setupEventHandlers | Child process stderr:     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
ChildProcessManager::setupEventHandlers | Child process stderr:     at async ModelDataClient.getData (.../model-data.js:144:24)
ChildProcessManager::setupEventHandlers | Child process stderr:     at async Asset.get (.../core-utils/block.js:18:32)
ChildProcessManager::setupEventHandlers | Child process stderr:     at async BIMSVF2Manifest.AEC (.../svf2-manifest.js:90:25)
ChildProcessManager::setupEventHandlers | Child process stderr:     at async GridAlignment.computeGridCorrection (.../grid-alignment.js:25:27)
ChildProcessManager::setupEventHandlers | Child process stderr:     at async Object.calculateOrigin (.../compare-utils.js:76:36)
ChildProcessManager::setupEventHandlers | Child process stderr:     at async CompareExtractor.compare (.../compare-extractor.js:62:43)
ChildProcessManager::setupEventHandlers | Child process stderr:     at async extractComparison (.../forge-agents/app.js:133:20) {
ChildProcessManager::setupEventHandlers | Child process stderr:   errorCode: 'API_NOT_FOUND'
ChildProcessManager::setupEventHandlers | Child process stderr: }

같은 stack 이 version 20 (prev_forge_urn) 에 대해서도 한 번 더 반복된다.

잡 자체는 success 종료 — 사용자 영향 없음:

BimRevisionService::REVISE-END (info)json
{
  "bim_id": 18044,
  "src_revision_id": 24593,
  "prev_revision_id": 24273,
  "si_trace_id": "676699f8-68e5-40ed-867a-4d3ee79f29a9",
  "elapsed_ms": 253285,
  "status": "success",
  "counts": { "total": 0, "modified": 0, "removed": 0, "exist": 0 }
}

REVISE-BEGIN 로그가 동일한 URN 을 가리키는 것을 확인:

BimRevisionService::REVISE-BEGIN (info, 13:44:07 KST)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",
  "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 의 404 URL 과 정확히 일치.

추가로 같은 시간대 stderr 라인들 중에는 Forge 와 무관한 AWS SDK v2 deprecation warning 도 status:error 로 잡혀 있는데, 같은 메커니즘이다:

text
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).

이는 child 의 stderr 가 error 레벨로 매핑되는 정책의 부작용을 추가로 보여준다 — Node 의 --trace-warnings deprecation notice 까지 error 로그가 된다.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 child process 의 stderr 가 무조건 logger.error 로 흐르기 때문에, child 내부에서 recoverable 한 404 (AECModelData.json API_NOT_FOUND) 가 parent 의 error 로그로 분류된다 child-process.manager.ts:138-141 가 모든 stderr chunk 를 logger.error 로 보냄 / 동일 시간 window 에 REVISE-END "status":"success", elapsed_ms:253285 가 기록됨 / AWS SDK v2 deprecation 노이즈까지 같은 형식으로 error 로 잡힘 Confirmed
H2 BIM revision 잡이 실제로 실패해서 사용자 영향이 발생한다 stack trace 가 extractComparisonCompareExtractor.compare 호출 경로에서 발생 BimRevisionService::REVISE-ENDstatus:"success" 로 종료 (Datadog 13:48:18 KST). Compare 결과 정상 반환 (modified/removed/exist 모두 0) Rejected
H3 BimCompareManager.execute 가 errorCode BimRevisionExtractorExecute 를 set 해서 호출자에게 전달했다 bim-compare.manager.ts:71 가 catch 에서 setErrorCode 호출 child IPC 가 result 를 정상 반환했으므로 parent 의 await this.childProcessManager.execute(...) 가 throw 하지 않음 → catch 블록 자체가 실행되지 않음. REVISE-END status:success 와 일관 Rejected
H4 Forge Model Derivative 일시 outage 가 root cause errorCode: 'API_NOT_FOUND' 가 한 객체에 대해서만 발생 다른 URN(bim_id=20016) revision 은 같은 시간대에 success 로 종료. 404 가 일시적이 아니라 그 특정 translation output 에 AECModelData.json 이 만들어지지 않은 영구 상태로 보임 Rejected
H5 동일 scope (svc:cupixworks-any-bimrevision-agent::unknown) 의 대량 발생 incident 가 별개의 root cause status-board 가 2026-06-24-svc-cupixworks-any-bimrevision-agent--unknown-1 에 20개 cluster 를 묶어둠 묶인 cluster 모두 같은 stderr passthrough 메커니즘에서 파생 (서로 다른 stack frame 이 분리된 청크로 도착해서 다른 fingerprint 가 됨) Rejected — 같은 root cause

Fix Recommendation#

즉시 조치 (Critical)#

없음. 사용자 영향이 없고 잡은 정상 종료된다. 운영 alarming 만 조정한다 — 이 fingerprint 로 페이지를 울리지 않도록 monitor 에서 제외하거나 status-board 에서 dismiss 처리.

단기 개선 (1주 이내)#

  1. applications/agents/packages/base/src/manager/child-process.manager.ts:138-141 의 stderr passthrough 정책을 재검토.
    • 방향: child 의 stderr 를 무조건 logger.error 로 흘리지 말고 logger.warn (또는 info) 로 다운그레이드하거나, 라인 패턴으로 분류하라. 예: errorCode: 'API_NOT_FOUND' 또는 node: NOTE: 같은 deprecation prefix 는 warn, 실제 fatal 만 error. 단, parent 가 child 의 정상 종료 여부(close event 의 code) 를 따로 보고 있으므로 stderr 자체는 진단 정보로 충분히 다뤄도 안전.
    • 근거: 같은 코드 경로(logger.error) 가 deprecation warning, recoverable 404, 진짜 crash 를 모두 같은 레벨로 묶고 있어 alert/triage 가 무의미해진다.
  2. BimCompareProcess::execute (bim-compare.process.ts:67-115) 의 catch 가 error.message/error.stackthis.log 로 IPC 통해 parent 로 보내는 구조와, 외부 패키지가 stderr 에 직접 인쇄하는 구조가 중복되어 있음 — child 안에서 console.error (또는 axios 의 default error 출력) 를 silence 하고 IPC 채널로만 보고하도록 정리.

장기 개선 (재발 방지)#

  1. classifier 측: Child process stderr: prefix 가 붙은 multi-line stack chunk 를 하나의 fingerprint 로 정규화하도록 룰 추가. 현재는 한 trace 가 ≥10 cluster 로 폭발한다 (status-board 의 incident 2026-06-24-svc-cupixworks-any-bimrevision-agent--unknown-1 에 20 cluster).
  2. @cupixapps/forge-utils 가 던지는 API_NOT_FOUND 를 BIM revision agent 가 일급 도메인 신호로 받아서, "이 translation 에는 AECModelData.json 이 없음 → grid alignment skip" 같은 식으로 명시적으로 처리하고 비-에러 로그로 기록하는 PR. 그러면 stderr 자체에도 stack 이 안 찍힌다.

Monitoring#

  • 추가/조정 모니터링:

    • bim-revision agent 실제 실패율 (REVISE-END status != success). 이 cluster 류 노이즈에 의존하지 않는 정확한 사용자 영향 지표:

      text
      sum:logs.hits\{service:cupixworks-any-bimrevision-agent,@evt.name:BimRevisionService.REVISE-END,@status:failure\}.as_count()
      
    • child process stderr 노이즈 양 (트렌드 확인용):

      text
      sum:logs.hits\{service:cupixworks-any-bimrevision-agent,status:error,@message:"Child process stderr*"\}.as_count()
      
    • Forge API_NOT_FOUND 빈도 (도메인 신호로 추적):

      text
      sum:logs.hits\{service:cupixworks-any-bimrevision-agent,status:error,@message:"API_NOT_FOUND"\}.as_count()
      
  • Datadog 검색 쿼리 (재현용):

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

Risk Assessment#

  • Risk level: low (사용자 영향 없음, REVISE-END success)
  • 예상 복잡도: standard (외부 패키지 영향 없이 parent 측 로깅 정책만 조정해도 노이즈 제거 가능)