TransferManager::downloadAppProcessingOptions | {"name":"HTTPError","host":"api-tesla.cupix.internal
RCA: TransferManager::downloadAppProcessingOptions | HTTPError 403 Forbidden
Error Log#
TransferManager::downloadAppProcessingOptions | {"name":"HTTPError","host":"api-tesla.cupix.internal","hostname":"api-tesla.cupix.internal","method":"GET","path":"/api/v1/captures/681313/resources/processing_options/download","protocol":"http:","url":"http://api-tesla.cupix.internal/api/v1/captures/681313/resources/processing_options/download","statusCode":403,"statusMessage":"Forbidden","headers":{...}}
Impact#
- Service:
cupixworks-capture-preprocessor-agent - Team: hodulee
- 발생 횟수: 1 (이 클러스터), 5+ (최근 14일간 동일 패턴)
- 최초 발생: 2026-04-15T02:29:45.344Z
- 최근 발생: 2026-04-15T02:29:45.344Z
Root Cause Summary#
capture-preprocessor-agent가 capture 681313의 processing_options 리소스를 다운로드하려 할 때, 해당 capture에 processing_options 리소스가 DB에 존재하지 않아 tesla API가 Cupix::Errors::NotFound를 발생시켰다. 그런데 tesla의 client_error_controller.rb에서 NotFound 예외를 보안상의 이유로 HTTP 403 Forbidden으로 매핑하고 있어, agent 측에서는 권한 오류인지 리소스 부재인지 구분할 수 없다. agent의 TransferManager::downloadAppProcessingOptions는 이 에러를 catch 후 false를 반환하고, PreprocessorService는 다운로드 실패를 무시한 채 나머지 전처리 파이프라인을 계속 진행한다. 즉, 이 에러는 처리 실패를 유발하지 않는 non-blocking 에러이지만, error 레벨로 로깅되어 불필요한 알림 노이즈를 생성하고 있다.
Technical Analysis#
Code Path#
- Entry point:
preprocessor-service.ts:93—run()메서드가 capture preprocessing 시작 - Download 호출:
preprocessor-service.ts:120—await this.downloadAppProcessingOptions(cpCapture)호출
// preprocessor-service.ts:120
await this.downloadAppProcessingOptions(cpCapture);
await this.downloadPanoFiles(cpCapture); // 다운로드 실패와 관계없이 계속 진행
- Wrapper 메서드:
preprocessor-service.ts:384-395— capture 타입이 App인 경우에만 다운로드 시도
// preprocessor-service.ts:384-395
private downloadAppProcessingOptions = async (cpCapture: CPCapture): Promise<void> => {
logger.debug('PreprocessorService::downloadAppProcessingOptions | begin');
if (!cpCapture.isTypeApp) {
logger.debug('PreprocessorService::downloadAppProcessingOptions | end - capture type is not app ');
return;
}
const isDone = await this.transferManager.downloadAppProcessingOptions(cpCapture);
if (isDone) await cpCapture.loadProcessingOptions();
logger.debug('PreprocessorService::downloadAppProcessingOptions | end');
};
- Failure point:
transfer.manager.ts:231-249— HTTP 403 에러 발생 시 catch하여 error 로그 출력 후false반환
// transfer.manager.ts:231-249
downloadAppProcessingOptions = async (cpCapture: CPCapture): Promise<boolean> => {
const path = cpCapture.appProcessingOptionsFilePath;
const url = cpCapture.appProcessingOptionsDownloadUrl;
const accessToken = this.cupixAuth.accessToken;
if (path == undefined || url == undefined) {
return false;
}
try {
fs.writeFileSync(path, await download(url, undefined, {
headers: { 'Content-Type':'application/json', 'X-CUPIX-AUTH': accessToken },
retries: 10
} as any));
return true;
} catch (error) {
logger.error('TransferManager::downloadAppProcessingOptions | %s', JSON.stringify(error));
return false; // 에러를 삼키고 false 반환 — 처리는 계속됨
}
};
- API 측 에러 원인:
multiple_resourcable_controller.rb:145-151—processing_options리소스가 DB에 없으면NotFound발생
# multiple_resourcable_controller.rb:145-151
def set_multiple_resource
kind_or_key = ApplicationRecord.sanitize_sql(params[:kind_or_key])
@resource = @model.resources.where(kind: kind_or_key).or(@model.resources.where(key: kind_or_key)).first
raise Cupix::Errors::NotFound.new(code: 'ARG10002', reason: 'Resource not found') and return if @resource.nil?
end
- 403 매핑:
client_error_controller.rb:27,55-57—NotFound를 403으로 매핑하는 보안 패턴
# client_error_controller.rb:27
rescue_from Cupix::Errors::NotFound, with: :not_found_403_error
# client_error_controller.rb:55-57
def not_found_403_error(exception)
raise_error(403, exception)
end
기대 동작: processing_options 리소스가 없는 capture에 대해서는 다운로드를 시도하지 않거나, warn 레벨로 로깅
실제 동작: 리소스 존재 여부와 관계없이 무조건 다운로드를 시도하고, 실패 시 error 레벨로 로깅
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-capture-preprocessor-agent status:error "TransferManager::downloadAppProcessingOptions"
Time range: 2026-04-01T00:00:00Z to 2026-04-15T12:00:00Z
14일간 최소 5건의 동일 패턴 에러가 확인됨 (captures: 681313, 40566, 40567, 40565, 71063):
{
"timestamp": "2026-04-15 11:29:45 KST",
"status": "error",
"message": "TransferManager::downloadAppProcessingOptions | {\"name\":\"HTTPError\",\"statusCode\":403,\"statusMessage\":\"Forbidden\",\"path\":\"/api/v1/captures/681313/resources/processing_options/download\"}"
}
API 측 로그 — 동일 요청에 대한 tesla API 응답:
service:cupixworks-api 681313 "processing_options"
Time range: 2026-04-15T01:00:00Z to 2026-04-15T04:00:00Z
{
"timestamp": "2026-04-15 11:29:47 KST",
"status": "info",
"message": "[403] GET /api/v1/captures/681313/resources/processing_options/download (Api::V1::CapturesController#download_multiple_resource)",
"error": {
"reason": "Resource not found",
"code": "ARG10002",
"message": "Resource not found",
"class": "Cupix::Errors::NotFound"
}
}
API 로그에서 실제 에러 클래스가 Cupix::Errors::NotFound임을 확인 — 즉, 권한 문제가 아니라 리소스가 DB에 존재하지 않는 것이 원인이다.
처리 계속 진행 확인 — 에러 발생 후 나머지 파이프라인이 정상 진행됨:
{"timestamp": "2026-04-15 11:29:45 KST", "status": "error", "message": "TransferManager::downloadAppProcessingOptions | ...403..."}
{"timestamp": "2026-04-15 11:29:51 KST", "status": "info", "message": "PreprocessorService::setVideoImageMatchData | begin - capture_id: 681313"}
{"timestamp": "2026-04-15 11:30:49 KST", "status": "info", "message": "PreprocessorService::runPreprocessor | photo count: 0, video count: 1, app AR file path: /tmp/workspace/681313/app/processing_options.json"}
{"timestamp": "2026-04-15 11:30:53 KST", "status": "info", "message": "BaseService::cleanUpAnythingRelatedModel | path: /tmp/workspace/681313"}
processing_options 리소스 부재는 광범위 패턴 — 단순 show 요청에서도 동일 에러 빈발:
service:cupixworks-api "processing_options" "Resource not found"
4월 15일 하루에만 다수의 capture(681320, 68677, 68676, 68675, 1714-1719 등)에서 동일한 Resource not found 응답 확인.
Fix Recommendation#
즉시 조치 (Critical)#
agent 측 로그 레벨 변경 — transfer.manager.ts:246
현재 logger.error로 기록되지만, 이 에러는 처리를 중단시키지 않는 expected condition이다. HTTP 403 (실제로는 리소스 부재)인 경우 logger.warn으로 변경하여 error 노이즈를 줄여야 한다. 추가로 statusCode를 로그 메시지에 명시적으로 포함하면 디버깅이 용이해진다.
단기 개선 (1주 이내)#
리소스 존재 여부 사전 확인 — preprocessor-service.ts:384-395
downloadAppProcessingOptions 호출 전에 capture의 리소스 목록을 조회하여 processing_options가 존재하는지 확인하는 로직을 추가하거나, loadCaptureResources(line 108)에서 이미 로드한 리소스 목록에 processing_options가 포함되어 있는지 체크한 후 다운로드를 시도해야 한다. 이렇게 하면 불필요한 HTTP 요청과 에러 로그를 원천 차단할 수 있다.
장기 개선 (재발 방지)#
-
API 응답 코드 개선 —
client_error_controller.rb:27,55-57에서NotFound를 403으로 매핑하는 보안 패턴은 외부 클라이언트에는 적절하지만, 내부 서비스 간 통신에서는 에러 원인 판단을 어렵게 만든다. 내부 API 호출 시에는 실제 에러 코드(ARG10002)와 reason(Resource not found)을 응답 body에 포함하고 있으므로, agent 측에서 이 body를 파싱하여 리소스 부재와 권한 오류를 구분할 수 있도록 개선할 수 있다. -
retries: 10설정 재검토 —transfer.manager.ts:242에서 리소스가 존재하지 않는 경우에도 10회 재시도를 수행한다. 403/404 등 클라이언트 에러에 대해서는 재시도하지 않도록 retry 정책을 개선해야 한다.
Monitoring#
- error 로그 노이즈 감소 확인:
service:cupixworks-capture-preprocessor-agent status:error "TransferManager::downloadAppProcessingOptions"
- 로그 레벨 변경 후 warn 레벨 모니터링:
service:cupixworks-capture-preprocessor-agent status:warn "TransferManager::downloadAppProcessingOptions"
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial — 이 에러는 처리 파이프라인을 중단시키지 않으며,
processing_options리소스가 없는 capture에서 expected condition으로 발생한다. 로그 레벨 변경만으로 즉시 노이즈를 줄일 수 있다.