fail to translation bim. BimRevision: 5194 Status: failed, Messages: [{"type"=>"error", "code"=>"Rev
RCA: fail to translation bim — Revit-UnsupportedFileType
Overview#
What Happened#
2026-07-13 16:44 KST에 cupixworks-worker 의 BimRevisionRepository#finish_forge_translating! 가 Autodesk Forge manifest 조회 결과 status: failed 를 받고 Cupix::Logger.error 로 로그를 기록했다. Forge 측 실패 사유는 Revit-UnsupportedFileType — 사용자가 업로드한 파일이 Revit 파일이 아니거나 지원하지 않는 버전이라는 뜻이다. 시스템 자체의 예외/스택트레이스는 없으며, 외부 시스템(Forge) 의 사용자 입력 검증 실패를 서비스가 그대로 error 레벨로 흘려보내고 있다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | - (예외 아님, 로그 메시지) |
| exception.message | fail to translation bim. BimRevision: 5194 Status: failed, Messages: [...Revit-UnsupportedFileType...] |
| top_frame | app/repositories/bim_revision_repository.rb:82 |
| logger | Cupix::Logger.error (class=BimRevisionRepository, function=finish_forge_translating!) |
| env | production, ap-southeast-2, tenant=cupix |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| BIM / Forge integration (cupixworks-worker) | 1 (해당 클러스터) / 지난 14일간 다수 (Revit-UnsupportedFileType 로 20+건 반복) |
개별 사용자의 BimRevision 하나가 error_forge_state 로 전이됨. 다른 사용자/기능 영향 없음. 지속적 error 로그로 인해 모니터링 noise 발생. |
Timeline#
- 2026-07-13 16:44:44 KST — Autodesk Forge webhook 이
forge_translation_finished_callback을 호출,FinishBimRevisionForgeTranslationWorker가 enqueue. - 2026-07-13 16:44:44 KST — Worker 가 Forge manifest 를 조회하고
status=failed,code=Revit-UnsupportedFileType를 수신,error_forge_state로 전이 후Cupix::Logger.error기록 (Datadog cluster8a242739). - 참고 — 동일 패턴이 2026-07-07 ~ 2026-07-13 사이 반복 발생 (BimRevision 3820, 5174, 5194, 6415~6429 등), status-board 상 active svc incident
2026-07-13-svc-cupixworks-worker--unknown-2의 구성 클러스터 중 하나.
Error Log#
fail to translation bim. BimRevision: 5194 Status: failed, Messages: [{"type"=>"error", "code"=>"Revit-UnsupportedFileType", "message"=>"<message>The file is not a Revit file or is not a supported version.</message>"}, {"type"=>"error", "message"=>"Possibly recoverable warning exit code from extractor: -536870935", "code"=>"TranslationWorker-RecoverableInternalFailure"}]
Impact#
- Service:
cupixworks-worker - 발생 횟수: 1 (이 클러스터), 유사
Revit-UnsupportedFileType로그는 최근 14일 20+건 - 최초 발생: 2026-07-13 16:44 KST
- 최근 발생: 2026-07-13 16:44 KST
- Blast radius: BimRevision 단일 레코드(5194). 사용자에게는 UI 에서 "번역 실패" 상태로 표시됨. 시스템 안정성/처리량 영향 없음.
Root Cause Summary#
Autodesk Forge Model Derivative API 가 사용자 업로드 파일에 대해 status: failed, code: Revit-UnsupportedFileType 를 반환했고, BimRevisionRepository#finish_forge_translating! 는 이 정상적(=예상 가능한) 외부 실패 응답을 Cupix::Logger.error 로 기록하도록 구현되어 있다. 즉 코드 결함이 아니라 사용자 입력 오류(비 Revit 파일 또는 미지원 버전) 를 error 레벨로 승격시키는 로깅 정책 이 원인이다. 워커는 이미 error_forge_state 로 상태를 전이시키며 예외를 던지지 않으므로 Sidekiq 재시도나 데이터 손상은 발생하지 않는다.
Technical Analysis#
Code Path#
- Entry point (webhook):
app/controllers/api/v1/bim_revisions_controller.rb:32—forge_translation_finished_callback이FinishBimRevisionForgeTranslationWorker.perform_async(@model.id)를 enqueue. - Worker:
app/workers/finish_bim_revision_forge_translation_worker.rb:5— BimRevision 을 조회 후finish_forge_translating!호출. - Failure point:
app/repositories/bim_revision_repository.rb:79-82— Forge manifest 응답status가'failed'인 branch 에서error_forge_state전이 후 error 로그.
def forge_translation_finished_callback
@model = ::BimRevision.find_by(forge_state: :translating, id: params[:id])
raise Cupix::Errors::NotFound.new(code: 'ARG10002', reason: 'BimRevision not found') if @model.blank?
FinishBimRevisionForgeTranslationWorker.perform_async(@model.id)
render_api
rescue Cupix::Errors::NotFound => e
raise e
rescue StandardError => e
raise Cupix::Errors::BadGateway.new(code: 'BG10001', reason: e.to_s)
end
class FinishBimRevisionForgeTranslationWorker
include Sidekiq::Worker
sidekiq_options queue: :default, retry: 1
def perform(bim_revision_id)
bim_revision = ::BimRevision.find_by(id: bim_revision_id)
return if bim_revision.blank?
bim_revision_repository = BimRevisionRepository.new(model: bim_revision)
bim_revision_repository.finish_forge_translating!
end
end
# Overall status of the translation jobs recorded in the "manifest". Possible values: pending, success, inprogress, failed, timeout
case _json_response['status']
when 'success'
if _svf == 'svf'
# if svf, another forge-agent will be launched and update to translated forge state
self.model.complete_forge_translate!
else
self.model.translated_forge_state
end
when 'failed', 'timeout'
self.model.error_forge_state
_messages = _bim_derivative['messages'] rescue nil
Cupix::Logger.error("fail to translation bim. BimRevision: #{self.model.id} Status: #{_json_response['status']}, Messages: #{_messages}", class: self.class.name, function: __method__, forge_response: _json_response)
else
Cupix::Logger.info("translation is done but status is not 'success' or 'failed'. BimRevision: #{self.model.id} Undefined Status: #{_json_response['status']}", class: self.class.name, function: __method__, forge_response: _json_response)
end
기대 동작: Forge 가 failed 를 리턴할 때는 (a) 사용자 입력 오류 (Revit-UnsupportedFileType, Revit-ProtectedFile, Revit-CorruptedFile 등 code 접두어가 Revit- 인 항목), (b) Forge 내부 일시 오류 (TranslationWorker-*), (c) 실제 시스템 문제로 구분되어야 하며, (a) 는 warn 이하 레벨로 낮추어 error 대시보드/알림에 노이즈를 남기지 않아야 함.
실제 동작: 모든 failed / timeout 응답을 일괄 Cupix::Logger.error 로 기록. Forge 응답 메시지에 실패 코드가 담겨 있음에도 코드별 분기 없음.
Log Evidence#
Datadog query (재현용):
service:cupixworks-worker "fail to translation bim"
이 클러스터의 원본 로그:
{
"timestamp": "2026-07-13 16:44:44",
"status": "error",
"message": "fail to translation bim. BimRevision: 5194 Status: failed, Messages: [{\"type\"=>\"error\", \"code\"=>\"Revit-UnsupportedFileType\", \"message\"=>\"<message>The file is not a Revit file or is not a supported version.</message>\"}, {\"type\"=>\"error\", \"message\"=>\"Possibly recoverable warning exit code from extractor: -536870935\", \"code\"=>\"TranslationWorker-RecoverableInternalFailure\"}]",
"class": "BimRevisionRepository",
"function": "finish_forge_translating!"
}
동일 코드 경로에서 반복 발생하는 최근 14일간의 표본 (query: service:cupixworks-worker "Revit-UnsupportedFileType"):
2026-07-13 16:44:44 BimRevision: 5194 Revit-UnsupportedFileType
2026-07-10 16:41:18 BimRevision: 5174 Revit-UnsupportedFileType
2026-07-10 14:45:36 BimRevision: 3820 Revit-UnsupportedFileType
2026-07-07 13:21:xx BimRevision: 6415~6429 (10+건) Revit-UnsupportedFileType
- 모든 로그가 동일한
class=BimRevisionRepository,function=finish_forge_translating!태그를 가짐 → 코드 경로 확정. - 실패 원인이 항상 사용자 입력(비 Revit 파일)이며, 스택트레이스가 없음 → 애플리케이션 예외 아님.
- 워커는 예외를 raise 하지 않으므로 Sidekiq 재시도가 발생하지 않고 job 은 성공으로 종료 — 예외 트래킹 도구가 아니라 log stream 에만 error 로 남는다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | Forge 가 사용자 업로드 파일을 Revit-UnsupportedFileType 으로 거절한 것을 서비스가 error 레벨로 로깅하는 것이 근본 원인 (사용자 입력 오류를 error 로 승격) |
Datadog 로그 메시지 원문에 code=Revit-UnsupportedFileType, message=The file is not a Revit file or is not a supported version 명시. bim_revision_repository.rb:79-82 에서 failed branch 가 무조건 Logger.error 호출. 동일 패턴이 14일간 20+건 반복. |
없음 | Confirmed |
| H2 | Autodesk Forge 서비스 장애 (외부 dependency outage) | 동일 시간대 동일 서비스에서 다른 종류의 Forge 에러 다수 발생했을 가능성 | 로그 메시지가 명시적으로 사용자 파일 형식 문제를 지목. TranslationWorker-RecoverableInternalFailure 는 부수 코드이나 Forge 는 Revit-UnsupportedFileType 를 primary error 로 반환. Forge status page 확인 필요하나, 사용자 입력 오류가 명시된 이상 dependency outage 로 볼 근거 없음. status-board 상 dep:* scope 아님 (svc scope). |
Rejected |
| H3 | Sidekiq worker retry / race condition 으로 이미 처리된 BimRevision 을 재처리 | finish_forge_translating! 은 return unless self.model.forge_state_translating? 로 guard (bim_revision_repository.rb:45). retry: 1 설정. |
로그가 1건만 발생, guard 문에 의해 재실행되어도 no-op. 여러 revision id 가 서로 다르므로 재시도 아님. | Rejected |
| H4 | Forge 인증(access_token) / manifest 조회 실패로 인한 서비스 결함 | HTTP 호출 라인이 있음 (bim_revision_repository.rb:47-64) |
manifest JSON 을 파싱하여 status, derivatives, messages 필드까지 접근에 성공. 즉 인증/네트워크 정상, Forge 는 정상적으로 실패 응답을 반환한 것. |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 파일:
app/repositories/bim_revision_repository.rb:79-82 - 접근 방식:
_bim_derivative['messages']에서code를 검사하여,Revit-접두어(사용자 파일 문제) 또는Revit-UnsupportedFileType/Revit-ProtectedFile/Revit-CorruptedFile등 사용자 입력 계열은Cupix::Logger.warn으로, 그 외 (TranslationWorker-*중 fatal, 인증 관련, 알 수 없는 상태) 는 기존대로Cupix::Logger.error유지. - 근거:
Revit-UnsupportedFileType은 정의상 사용자 업로드가 잘못된 경우이며, 이를 error 로 유지하면 대시보드/알림이 사용자 파일 오류로 오염된다. Memory 의 "RCA Quality Patterns — Assess error severity" 가이드와 일치. - 주의: 사용자 상태 전이(
error_forge_state) 는 유지해야 UI 에서 "번역 실패" 로 표시된다. 로그 레벨만 낮춘다.
단기 개선 (1주 이내)#
- Forge 응답 코드 분류 helper 추가 —
app/repositories/bim_revision_repository.rb인접 (혹은lib/cupix/forge/하위) 에classify_forge_failure(messages) -> :user_input | :forge_transient | :system형태 유틸을 만들어 로그 분기 로직을 재사용 가능하게 한다.BimRepository#finish_forge_translating!(bim_repository.rb:63) 도 동일 로직이 있는지 확인 후 함께 적용. - BimRevision 실패 사유를 모델 필드에 저장 — 현재는 로그로만 실패 이유가 남으므로, 사용자가 UI 에서 "왜 실패했는지" 알 수 없다.
error_forge_state전이 시_messages를bim_revision.forge_error_code등 컬럼에 저장하여 사용자 안내 문구를 개선 (별도 스토리로 프런트/PM 협의 필요 — 자동화 fix 대상 아님).
장기 개선 (재발 방지)#
- Error taxonomy 문서화 — 외부 dependency 응답을
error로 로그할지warn으로 로그할지에 대한 팀 가이드라인 (예: "사용자 입력 유효성 실패는 warn, 외부 서비스 5xx/timeout 은 error"). 반복적으로 RCA 후에 log-level 을 downgrade 하는 episode 가 축적되고 있음. - Datadog Error Tracking 필터 —
Revit-UnsupportedFileType등 사용자 입력 계열 실패는 Error Tracking 대시보드에서 제외되도록 tag/filter 규칙을 설정. 로그 레벨 변경과 병행하면 앞으로 클러스터 자체가 생성되지 않는다.
Monitoring#
Datadog release dashboard widget 용 timeseries 쿼리:
logs("service:cupixworks-worker @class:BimRevisionRepository @function:finish_forge_translating! status:error").index("*").rollup("count").by("@forge_response.derivatives.messages.code")
- 위 쿼리는 실패 코드별 시계열을 보여주어
Revit-UnsupportedFileType비중과 그 외 실제 시스템 에러를 분리 가시화한다. - 픽스 배포 후 검증:
Revit-*코드는 error → warn 으로 이동하고, 잔여 error 로그는 실제 시스템 에러여야 함.
부가 검증 쿼리 (레벨 이동 확인):
logs("service:cupixworks-worker @class:BimRevisionRepository @function:finish_forge_translating! status:warn \"Revit-UnsupportedFileType\"").index("*").rollup("count")
Risk Assessment#
- Risk level: low — 로그 레벨 변경 위주, 비즈니스 로직/상태 전이 미변경.
- 예상 복잡도: trivial — 단일 파일, 조건부 로거 호출 변경. 유사 지점(
BimRepository) 도 함께 검토 필요하나 동일 패턴이면 소규모 diff. - 반영 시 주의:
_bim_derivative['messages']는 배열이며code가 없거나 형태가 다를 수 있으므로Array(_messages).any? { |m| m['code'].to_s.start_with?('Revit-') }형태의 안전한 접근 필요. Sorbet/typecheck 없이 Ruby 관례로 처리.