PanoPostprocessorService::run | mask pano id:90201594 | error {"stack":"HttpError: HTTP request fail
RCA: PanoPostprocessorService::run mask pano HttpError 400
Overview#
What Happened#
2026-06-26 09:47~09:48 KST 사이 cupixworks-pano-postprocessor-instance (us-west-2)에서 PanoPostprocessorService::run의 mask 단계가 22건의 pano에 대해 HttpError statusCode:400으로 실패했다. 모든 실패는 panoApi.js:375 (SDK checkMaskUploading 호출)에서 발생하며, response body 가 빈 {} 로 직렬화된 채 throw 된다. 실패한 pano 들은 panoPostprocessorManager.updatePanoState(...Error)로 표시되었지만 전체 capture 흐름은 중단되지 않고 진행되었다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | HttpError |
| exception.message | HTTP request failed |
| top_frame | node_modules/@tesla/typescript-node-sdk/api/panoApi.js:375:40 |
| failing call | cupixApi.pano.checkMaskUploading(panoId, maskType) (PUT /panos/{id}/check_mask_uploading) |
| caller | MaskWork.changeMask → pano-postprocessor-service.ts:135 |
| runtime | Node SDK @tesla/typescript-node-sdk@1.13.3-SNAPSHOT.202605110829 |
| env | production, us-west-2 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| rsconstruction / pano postprocessing | 22 | 22개 pano 가 mask 단계에서 실패하여 pano.state = Error로 기록됨. 동일 capture 내 다른 단계(infer/resize)는 계속 진행되어 capture 자체는 완료될 수 있으나, 해당 pano 들은 mask 미적용 상태가 된다. |
Timeline#
- 2026-06-26 09:46:59 KST —
MaskWork::maskPanos의face-body-detectorpython script 가UserWarning: No PYTORCH_KERNEL_CACHE_PATH or HOME environment variable set!를 stderr 로 출력 (script 자체는 계속 실행). - 2026-06-26 09:47:07 KST — 동일 script 가
RuntimeWarning: overflow encountered in exp를 출력 (logits → sigmoid 변환 시 overflow). 이 warning 들은 fatal 이 아니지만 일부 pano 의 mask 결과 품질에 영향 가능. - 2026-06-26 09:47:43 KST — 첫
mask pano id:90201594 | error ... statusCode:400발생 (clusterfirst_seen). - 2026-06-26 09:47:48 ~ 09:48:39 KST — 동일 패턴의 400 에러가 21건 추가 발생 (pano id 90201594, 90201496, 90201494, 90201401, 90201398, 90201397, 90201305, 90201303, 90201216, 90201092, 90201086, 90201088, 90200811, 90200326, 90200324, 90200315, 90200072, 90200073, 90199912, 90199913, 90199914, 90199915 등).
- 2026-06-26 09:48:02 KST — 한 인스턴스에서
pano.postprocess.summary&dd_total:971&dd_completed:971&dd_errored:0출력 (다른 capture instance 는 정상 종료). 실패는 다른 capture instance 들에 국한됨.
Error Log#
PanoPostprocessorService::run | mask pano id:90201594 | error {"stack":"HttpError: HTTP request failed
at Request._callback (/tmp/agent/dist/node_modules/.pnpm/@tesla+typescript-node-sdk@1.13.3-SNAPSHOT.202605110829_c0aaa38c9a9fff396d4124c32a1f39fa/node_modules/@tesla/typescript-node-sdk/api/panoApi.js:375:40)
at self.callback (/tmp/agent/dist/node_modules/.pnpm/request@2.88.2/node_modules/request/request.js:185:22)
at Request.emit (node:events:524:28)
at Request.<anonymous> (/tmp/agent/dist/node_modules/.pnpm/request@2.88.2/node_modules/request/request.js:1154:10)
at IncomingMessage.<anonymous> (/tmp/agent/dist/node_modules/.pnpm/request@2.88.2/node_modules/request/request.js:1076:12)
at IncomingMessage.emit (node:events:536:35)","message":"HTTP request failed","response":{"body":{},"statusCode":400},"body":{},"statusCode":400,"name":"HttpError"}
Impact#
- Service:
cupixworks-pano-postprocessor-instance - Team: rsconstruction
- 발생 횟수: 22
- 최초 발생: 2026-06-26 09:47:43 KST
- 최근 발생: 2026-06-26 09:48:39 KST
22개 pano 가 pano.state = Error로 기록된다. 사용자 가시적 영향: 해당 pano 들은 face/body mask 처리가 누락된 상태로 capture 에 남는다. mask 가 누락된 pano 는 후속 viewer/review 단계에서 face blur 처리가 안 된 원본이 노출될 수 있어 프라이버시 측면에서 follow-up 이 필요하다.
Root Cause Summary#
pano-postprocessor-service.ts:133 의 MaskWork.changeMask 가 호출하는 cupixApi.pano.checkMaskUploading (PUT /panos/{id}/check_mask_uploading) 가 HTTP 400 을 반환한다. 서버측 MaskableRepository#check_mask_uploading 는 mask.mask_uploaded? (S3 object 존재 여부) 가 false 일 때 Cupix::Errors::InvalidState (code: STAT10000, "Mask does not uploaded") 를 raise 하며 이 예외는 ClientErrorController#client_400_error 핸들러에서 400 으로 매핑된다. 즉 직전 단계인 awsS3Manager.uploadBySignedUrl(mask.uploadUrl, cpPano.maskImagePath) 가 실제로는 S3 에 mask 이미지를 올리지 못했거나 (maskImagePath 는 존재하지만 0-byte / 손상 상태), PUT 자체는 200 으로 응답했지만 S3 가 객체를 영속화하기 전에 후속 checkUploading 이 호출되었기 때문에 객체 lookup 이 실패한 것이다. (master 의 aws-s3.manager.ts:98-101 는 if (response.status !== 200) return; 분기를 가지지만, axios 의 기본 validateStatus 는 4xx/5xx 를 reject 하므로 이 분기는 실제로 도달 불가능이다 — Revision 2 에서 정정.) MaskWork.maskPanos 스크립트가 동일 시간대에 RuntimeWarning: overflow encountered in exp 와 No PYTORCH_KERNEL_CACHE_PATH warning 을 출력한 것도 일부 pano 에 대해 mask 결과 PNG 생성 자체가 불완전했을 가능성을 시사한다.
Technical Analysis#
Code Path#
Entry point: packages/cupix-pano-postprocessor/src/pano-postprocessor-service.ts:130-141
const maskTasks = cpPanos.map((cpPano) => {
return PARALLEL_TASK_LIMIT(async () => {
try {
await this.maskWork.changeMask(cpPano, maskTypeOption);
} catch (error) {
logger.error('PanoPostprocessorService::run | mask pano id:%d | error %s', cpPano.panoId, stringifyError(error));
await this.panoPostprocessorManager.updatePanoState(cpPano.panoId!, TESLA.UpdatePanoRequest.StateEnum.Error);
erroredPanoIds.add(cpPano.panoId!);
}
});
});
await Promise.all(maskTasks);
changeMask 의 3-step 흐름:
async changeMask(cpPano: CPPano, maskType: 'face' | 'facebody') {
if (!cpPano.panoId || !cpPano.maskImagePath || !fs.existsSync(cpPano.maskImagePath)) return;
const mask = await this.createMask(cpPano.panoId, maskType);
if (!mask) return;
await this.awsS3Manager.uploadBySignedUrl(mask.uploadUrl, cpPano.maskImagePath); // (1) S3 PUT
await this.checkUploading(cpPano.panoId, maskType); // (2) FAILS 400
await this.updateMaskType(cpPano.panoId, mask.maskId);
}
문제의 silent swallow:
async uploadBySignedUrl(signedUrl: string, imagePath: string) {
const agent = new https.Agent({ keepAlive: true });
const fileSize = fs.statSync(imagePath).size;
// ...
const fileStream = fs.createReadStream(imagePath);
const response = await axios.put(signedUrl, fileStream, {
httpsAgent: agent,
headers: { 'Content-Type': 'application/octet-stream', 'Content-Length': fileSize }
});
if (response.status !== 200) {
logger.error('AwsS3Manager::uploadBySignedUrl | upload fail - %s %s', imagePath, JSON.stringify(response.statusText));
return; // ← non-200 인데 throw 하지 않고 silent return
}
// ...
return true;
}
Failure point — SDK 호출 지점:
return interceptorPromise.then(() => {
// ...
const requestPromise = () => new Promise((resolve, reject) => {
request_1.default(localVarRequestOptions, (error, response, body) => {
if (error) { reject(error); }
else {
body = models_1.ObjectSerializer.deserialize(body, "MaskResponse");
if (response.statusCode && response.statusCode >= 200 && response.statusCode <= 299) {
resolve({ response: response, body: body });
}
else {
reject(new apis_1.HttpError(response, body, response.statusCode)); // ← line 375
}
}
});
});
return this.cupixRetriableRequest(requestPromise);
});
서버측 400 발생 지점:
def check_mask_uploading(mask_type)
_mask_type = mask_type.presence || 'custom'
mask = @model.masks.find_by_mask_type(_mask_type)
raise Cupix::Errors::NotFound.new(code: 'ARG10002', reason: "Mask not found. mask_type: #{mask_type}") if mask.nil?
if mask.mask_uploaded?(mask_revision: mask.mask_upload_revision)
mask.uploaded
# ... happy path: transition to uploaded, save, return mask ...
else
mask.missing
@model.missing_mask_state!
raise Cupix::Errors::InvalidState.new(code: 'STAT10000', reason: 'Mask does not uploaded') # ← 400 raise
end
# ...
end
400 매핑:
rescue_from Cupix::Errors::Unknown,
Cupix::Errors::Resource,
Cupix::Errors::Session,
Cupix::Errors::Parameter,
Cupix::Errors::Entity,
Cupix::Errors::Billing,
Cupix::Errors::InvalidState,
Cupix::Errors::Siteinsights, with: :client_400_error
def client_400_error(exception)
raise_error(400, exception)
end
기대 동작: uploadBySignedUrl 가 PUT 성공 후 S3 object 가 존재하므로 mask_uploaded? 가 true 를 반환하고 check_mask_uploading 가 200 으로 응답.
실제 동작: uploadBySignedUrl 가 실패했거나 (non-200, 또는 0-byte stream, 또는 axios timeout/abort) 정상 종료했지만 S3 가 객체를 가지고 있지 않은 상태에서 check_mask_uploading 호출 → 서버가 S3 lookup 실패 → 400.
Log Evidence#
Datadog query (재현용):
service:cupixworks-pano-postprocessor-instance status:error "mask pano"
service:cupixworks-pano-postprocessor-instance
from: 2026-06-26T00:45:00Z to: 2026-06-26T00:50:00Z
핵심 로그 (시간 흐름):
MaskWork::maskPanos | script /tmp/lib/face-body-detector/sam3/model/encoder.py:358: UserWarning: No PYTORCH_KERNEL_CACHE_PATH or HOME environment variable set! This disables kernel caching.
MaskWork::maskPanos | script /tmp/lib/face-body-detector/cupix_inference.py:266: RuntimeWarning: overflow encountered in exp
sigmoid_mask = 1.0 / (1.0 + np.exp(-logits_resized))
PanoPostprocessorService Elapsed Time - dd_step:blur_panos&dd_elapsed_time:1908158ms
PanoPostprocessorService Elapsed Time - dd_step:blur_panos&dd_elapsed_time:667008ms
PanoPostprocessorService Elapsed Time - dd_step:blur_panos&dd_elapsed_time:665051ms
22건의 동일 stack trace 에러 중 대표:
{
"message": "PanoPostprocessorService::run | mask pano id:90201594 | error ...",
"response": { "body": {}, "statusCode": 400 },
"body": {},
"statusCode": 400,
"name": "HttpError"
}
body 가 빈 {} 인 것은 cupixworks-api 가 비어있는 응답을 보냈기 때문이 아니라 SDK 가 응답을 models_1.ObjectSerializer.deserialize(body, "MaskResponse") 로 변환하면서 (panoApi.js:370) MaskResponse 스키마에 없는 code/reason/message 필드를 제거했기 때문이다. 따라서 로그에서 정확한 STAT10000 코드/메시지가 직접 보이지 않는다 — uncertain (실제 응답 body 검증을 위해 cupixworks-api 측 access log 가 필요하나 동일 시간대 service:cupixworks-api 검색으로는 매칭되는 항목을 찾지 못함, 14일 기준).
cupixworks-api 측 상관 로그 부재:
service:cupixworks-api status:(error OR warn) (90201594 OR 90199912)
Found 0 logs
→ api 가 400 으로 거절했더라도 error level 로 남기지 않았거나 별도 access log 채널을 통해 기록되었음. 이는 STAT10000 raise 가 통제된 흐름 (rescue_from → 400) 으로 처리되어 application-level error log 가 남지 않는 동작과 부합한다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | checkMaskUploading 시점에 S3 에 mask 객체가 없어 MaskableRepository#check_mask_uploading 가 InvalidState (STAT10000) 를 raise → 400. 원인은 직전 uploadBySignedUrl 가 실패했음에도 silent return 한 것 (또는 PUT 자체가 timeout/abort). |
(a) 서버 코드에서 mask_uploaded? false 시 STAT10000 raise, ClientErrorController 가 STAT10000 → 400 매핑 (maskable_repository.rb:42-46, client_error_controller.rb:13-14). (b) aws-s3.manager.ts:99-100 가 non-200 응답에 throw 없이 return. (c) face-body-detector python script 가 동일 시간대 RuntimeWarning(overflow) 출력 — 일부 mask PNG 가 비정상일 가능성. (d) 동일 stack trace 22건 모두 panoApi.js:375 (checkMaskUploading) 에서 발생. |
response body 가 SDK 직렬화로 비워져 STAT10000 코드를 로그에서 직접 확인 불가 — uncertain, but 매핑 로직으로 가장 정합 | Confirmed (primary) |
| H2 | createMaskUploadUrl (mask_upload_url repository) 가 uploading_mask_state! 에서 StateMachines::InvalidTransition 을 raise → 400. |
mask_state 가 special state 일 때 이론적으로 가능 | (a) state machine 정의상 event :uploading do transition any => :uploading end 로 어떤 상태에서도 :uploading 으로 전이 허용 (statable/pano.rb:196-198). (b) SDK 호출 stack 의 line 375 deserialization 타입이 MaskResponse 인 메서드는 createMaskUploadUrl 와 checkMaskUploading 둘 다 해당하지만, changeMask 흐름에서 createMask 가 정상 반환된 후 uploadBySignedUrl 가 실행됐다면 mask 객체가 만들어진 상태이므로 후속 check 단계가 실패 지점. |
Rejected |
| H3 | panoApi.js:375 는 update(panoId, { mask_id }) (mask-work.ts:99) 의 실패. |
changeMask 의 세 번째 await | (a) update SDK 메서드의 reject line 은 다른 위치이며 deserialize 타입이 PanoResponse 인 것이 일반적. (b) 22건 모두 동일 line 375 + MaskResponse 직렬화 흐름. |
Rejected |
| H4 | cupixworks-api 다운/디플로이/rate-limit 등 외부 dependency 이슈. | — | status-board 조회 결과 svc:cupixworks-pano-postprocessor-instance::unknown 에 active incident 없음; service:cupixworks-api status:error 동일 시간대에 mask 관련 에러 없음; 동일 인스턴스 다른 capture 들은 dd_errored:0 으로 성공 종료. |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
origin/develop적용 우선 고려 — develop 에는 이 클러스터의 업로드/마스크 경로를 직접 건드리는 미머지 커밋이 있다 (origin/master..origin/develop비교 결과). 자세한 효과 분석은 아래 "develop 적용 효과 분석" 참조. 운영 적용 가능 시 (a) TSLA-13233 (567dd4802) 의uploadBySignedUrl재구현 + 재시도, (b) TSLA-12759 (add674797) 의getCPPanosByCaptureId필터, (c)mask-work.ts의 stderr 캡쳐 +Errorreject 를 한 번에 가져온다.applications/agents/packages/cupix-pano-postprocessor/src/manager/aws-s3.manager.ts:91-99 (master)의axios.put응답 처리: master 의if (response.status !== 200) return;분기는 사실상 도달 불가다 (axios 의validateStatus기본값은 4xx/5xx 를 reject 함). 따라서 "silent swallow" 는 4xx/5xx 자체에 대해서는 일어나지 않지만, mid-stream EPIPE/ECONNRESET 같은 transient 네트워크 오류 는 axios 가 즉시 throw 하여 retry 없이 실패로 끝난다. develop 의@agents/base/uploadFile는 이를 3회 지수 백오프로 재시도한다 (packages/base/src/util/transfer.ts:11-33).applications/agents/packages/cupix-pano-postprocessor/src/work/mask-work.ts:97-98 (master):uploadBySignedUrl가 정상 throw 한 경우 이미changeMask의 catch 로 흘러간다. 그러나 mask PNG 가 0-byte 거나 (filesystem 에는 존재) PUT 자체는 200 으로 끝났지만 S3 가 객체를 가지고 있지 않은 보기 드문 케이스에서는checkUploading이 400 으로 반환된다. 이 경로에 대한 보강이 별도로 필요하다 —fs.statSync(maskImagePath).size > 0pre-check 를 추가하라.
단기 개선 (1주 이내)#
face-body-detectorpython script 의RuntimeWarning: overflow encountered in exp처리:cupix_inference.py:266의 sigmoid 계산을scipy.special.expit또는 logits clipping 으로 변경하여 NaN/비정상 mask PNG 가 생성되지 않도록 한다 (uncertain — 실제 PNG 출력 손상 여부는 별도 검증 필요).MaskWork.changeMask의fs.existsSync(cpPano.maskImagePath)체크에 file size > 0 검증을 추가 (fs.statSync(...).size > 0). 0-byte mask 가 S3 에 올라가도 check_mask_uploading 은 객체 존재만 보므로 통과할 수 있어 별도 검증 가치 있음.panoApi.js:370직렬화로 인해 4xx 응답 body 가 사라지는 문제 —@tesla/typescript-node-sdk의 reject path 에서 error response body 는 raw 로 전달하도록 codegen template 조정 (cupix-apirepo). 현재 운영 디버깅 시 STAT10000 같은 코드가 보이지 않아 RCA 가 어려워진다.
장기 개선 (재발 방지)#
agents의 fix logger convention 에 맞춰stringifyError(error)대신 logger 에 Error 객체를 직접 넘기는 패턴으로 통일 (memory 노트 참조 — agents 코드베이스는 fix logger 가 Error 객체를 native 로 처리).cupixApi.pano.checkMaskUploading에 대한 짧은 retry/backoff 추가 (이미cupixRetriableRequest가 있으므로 STAT10000 같은 일시적 객체 미존재 케이스에 한해 1-2회 retry). 단, retry 가 silent S3 PUT 실패를 가리지 않도록 H1 의 즉시 조치를 먼저 적용해야 한다.- pano 단위 mask 실패에 대한 dashboard/alert (아래 Monitoring 참조).
develop 적용 효과 분석#
origin/master..origin/develop 의 applications/agents/packages/cupix-pano-postprocessor/ 디렉토리에는 5개 파일에 걸친 변경이 미머지 상태로 존재한다 (commits: 567dd4802, add674797, 60faccf97, ef4029946, f2f3f24e6, eb940111f, b849d81e6, 8fbf55736, ...).
| 커밋 | 변경 요약 | 이번 incident 와의 관련성 |
|---|---|---|
567dd4802 TSLA-13233 — unify S3/signed-URL uploads onto @agents/base retry infra |
uploadBySignedUrl 가 @agents/base/util/transfer.ts:55-63 uploadFile 호출로 교체됨. interceptor 가 ECONNRESET/ETIMEDOUT/ECONNABORTED/EPIPE/EAI_AGAIN 및 5xx 에 대해 3회 지수 백오프 재시도 (base delay 1000ms), 요청 타임아웃 60s. 실패 시 throw error 로 명시 전파. |
부분 도움 — Datadog 에서 동일 시간대 ECONNRESET/ETIMEDOUT/EPIPE 검색 결과 0건 (service:cupixworks-pano-postprocessor-instance 14일 기준). 이번 incident 는 transient 네트워크 실패로 보이지 않아 retry 가 직접 해결하지는 않는다. 다만 향후 재발 방지 측면에서 가치 있음. |
add674797 TSLA-12759 — Filter panos with resource_state !== Uploaded in getCPPanosByCaptureId |
pano-postprocessor-manager.ts:31-38 에서 source image 가 S3 에 완전히 올라가지 않은 pano 를 처리 대상에서 제외. (Mask 결과가 0-byte/손상되어 후속 checkUploading 에서 400 이 발생하는 케이스 차단) |
유의미 — 이번 incident 는 22개 pano 가 동시에 같은 시간대에 실패. 한 capture 의 다수 pano 가 동시에 mask 단계에서 400 을 받았다면, 일부는 source image 가 미완성 업로드 상태였을 가능성이 있다. 단, source image 의 resource_state 를 직접 확인하려면 Kibana 검증이 필요 (이번 revision 에서는 수행하지 않음). |
mask-work.ts stderr 캡쳐 + reject(new Error(message)) |
마스크 스크립트 stderr 누적 후, 종료 코드가 0 이 아닐 때 마지막 500 자를 포함한 Error 로 reject. 기존 reject(false) 는 caller 의 catch 가 의미있는 메시지를 받지 못함. |
진단 개선 — 이번 incident timeline 의 RuntimeWarning: overflow encountered in exp 가 종료 코드 != 0 으로 이어졌는지, 단순 warning 이었는지 구분할 수 있게 된다. RCA 가 좀 더 빨라짐. |
ef4029946/8fbf55736/36f7326b8 TSLA-12479 — AWS SDK v2 → v3 마이그레이션 |
download / uploadDirectoryByCredential 가 S3Client + Upload (@aws-sdk/lib-storage) 로 전환. uploadBySignedUrl 자체는 axios PUT 이므로 영향 없음. |
이번 incident 와 무관 — 이번 실패 지점은 uploadBySignedUrl (signed URL PUT). download/credential upload 경로가 아니다. |
60faccf97 — bucket_region for S3 client signing region |
credential 기반 업로드의 signing region 수정. | 이번 incident 와 무관 — signed URL PUT 은 region 결정을 S3 측 URL host 가 담당. |
결론: develop 을 그대로 master 에 가져오면 (1) signed-URL 업로드의 transient 네트워크 실패에 대한 자동 retry 가 생기고, (2) source image 가 미완성 업로드인 pano 는 mask 단계에 진입조차 하지 않으며, (3) mask 스크립트가 비정상 종료할 경우 실제 에러 메시지가 로그/예외에 남게 된다. 이번 incident 의 22건 중 transient 네트워크 케이스는 로그상 0건이므로 (1) 의 즉효는 제한적이지만, (2) 가 root cause 후보 중 "S3 에 source image 가 미완성 업로드인 pano 가 처리됨" 시나리오를 차단하고, (3) 가 향후 동일 증상의 RCA 속도를 크게 단축시킨다 — 따라서 개선 효과는 있지만 incident 의 모든 케이스를 자동으로 해결한다고 단정할 수는 없다. 추가로, 마스크 PNG 의 file size > 0 검증이 develop 에도 누락되어 있어 0-byte mask 시나리오는 develop 적용 후에도 별도 패치가 필요하다.
Monitoring#
- Datadog metric:
pano.postprocess.summary의dd_errored가 0 이 아닌 capture 비율을 추적. - 로그 기반 알람: 다음 쿼리 결과가 임계치를 넘으면 알림.
service:cupixworks-pano-postprocessor-instance status:error "mask pano"
service:cupixworks-pano-postprocessor-instance "AwsS3Manager::uploadBySignedUrl | upload fail"
service:cupixworks-pano-postprocessor-instance "MaskWork::maskPanos" "RuntimeWarning"
(각 쿼리는 release dashboard timeseries widget 에 그대로 입력 가능 — pipe/stats 문법 미사용)
Risk Assessment#
- Risk level: medium — 단일 capture 의 mask 단계가 부분 실패하면 face/body blur 가 누락된 pano 가 production 에 노출될 수 있다 (privacy 영향). 다만 전체 capture pipeline 이 중단되지는 않음.
- 예상 복잡도: standard —
origin/develop의 관련 커밋 (TSLA-13233, TSLA-12759, mask-work stderr 캡쳐) 을 master 로 가져오는 작업이 가장 자연스러운 즉시 조치이며, 잔여 위험 (0-byte mask PNG, sigmoid overflow) 은 별도 패치가 필요.
Revision History#
Revision 1#
Feedback: "origin/develop 에 적용된걸 적용하면 업로드 문제가 개선될까" — develop 에 머지된 변경을 master 로 가져오면 이번 업로드 실패가 개선되는지 확인 요청.
판정:
| 피드백 항목 | 판정 | 근거 |
|---|---|---|
| develop 의 변경 적용 시 업로드 문제 개선 여부 | 부분 수용 | origin/master..origin/develop 의 applications/agents/packages/cupix-pano-postprocessor/ 에는 5개 파일 변경이 미머지로 존재한다. (a) TSLA-13233 567dd4802: uploadBySignedUrl 가 @agents/base/util/transfer.ts:55-63 uploadFile 로 교체되어 ECONNRESET/ETIMEDOUT/ECONNABORTED/EPIPE/EAI_AGAIN/5xx 에 대해 3회 지수 백오프 retry + 60s timeout 적용 (packages/base/src/util/transfer.ts:5-33). (b) TSLA-12759 add674797: getCPPanosByCaptureId 가 resource_state !== Uploaded pano 를 처리 대상에서 제외 (pano-postprocessor-manager.ts:31-38). (c) mask-work.ts 가 child process stderr 를 누적해 Error(message) 로 reject. → 업로드 신뢰성 (retry) 과 진단성 (stderr 캡쳐) 은 개선되지만, Datadog 검색 결과 동일 시간대 ECONNRESET/ETIMEDOUT/EPIPE/socket hang up 0 건 (service:cupixworks-pano-postprocessor-instance 14일 기준) — 이번 22건의 400 이 transient 네트워크 실패였다는 직접 증거는 없다. 따라서 retry 가 이번 incident 의 모든 케이스를 자동으로 해결하지는 않는다. (b) 의 source-image 미완성 업로드 필터는 root cause 후보 중 하나를 차단하지만 별도 Kibana 검증이 필요하다. develop 에도 mask PNG file size > 0 검증은 누락되어 있으므로 0-byte mask 시나리오는 develop 적용 후에도 추가 패치 필요. |
변경 사항:
Root Cause Summary의 "silent swallow" 단정을 정정 — axiosvalidateStatus기본값으로 4xx/5xx 는 이미 throw 되므로if (response.status !== 200) return;분기는 도달 불가. 실제 가설은 (i) 0-byte/손상 mask PNG, (ii) S3 eventual consistency 이슈, (iii) transient 네트워크 실패 후 재시도 부재.Fix Recommendation > 즉시 조치최상단에 "origin/develop 적용 우선 고려" 항목 추가.- 새 subsection
### develop 적용 효과 분석추가 — 미머지 커밋별 변경 요약, 이번 incident 와의 관련성 표. Risk Assessment의 예상 복잡도 문구를 "silent return → throw" 에서 develop 적용 기반 표현으로 정정.
추가 조사 내용:
- 레포 탐색:
$REPOS_DIR/cupixworks에서origin/master..origin/develop차이 확인 (git log/git diff). - 신규 코드 경로:
applications/agents/packages/base/src/util/transfer.ts(axios interceptor 기반 retry 로직),applications/agents/packages/base/src/manager/transfer.manager.ts(re-export). - Datadog 검색:
service:cupixworks-pano-postprocessor-instance (ECONNRESET OR ETIMEDOUT OR EPIPE OR "socket hang up")→ 0건;service:cupixworks-pano-postprocessor-instance "upload fail"→ 0건. transient 네트워크 실패 /uploadBySignedUrl | upload fail로그 없음.