Expected positive integer for bottom but received -96409 of type number
RCA: Expected positive integer for bottom but received -96409 of type number
Overview#
What Happened#
2026-05-26 13:24:47 UTC에 cupixworks-any-floorplan-agent 서비스에서 floorplan 87012를 타일링하는 과정에서 sharp 라이브러리의 extend() 호출 시 bottom 파라미터에 음수 값(-96409)이 전달되어 에러가 발생했다. 이미지의 실제 높이가 maximumPixels (65,536)보다 커서 extend 계산 결과가 음수가 된 것이 원인이다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Error |
| exception.message | Expected positive integer for bottom but received -96409 of type number |
| top_frame | sharp/lib/resize.js:412 → FloorplanService.tileFloorplan (app.cjs:6882) |
| runtime | Node.js, sharp@0.33.5 |
| env | production, us-west-2 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| aec-on (team 657) | 1 | Floorplan 87012 타일링 실패, 사용자(mvillamor@aec-on.com)의 floorplan 처리 완료 불가 |
Timeline#
- 13:24:18 UTC — 세션 시작, floorplan 87012 처리 개시 (state:
resource_uploaded) - 13:24:18 ~ 13:24:47 UTC — 이미지 다운로드 및 translate 단계 수행
- 13:24:47.716 UTC —
tileFloorplan시작 - 13:24:47.729 UTC — sharp
extend()호출 시 음수bottom값으로 에러 발생 - 13:24:47.730 UTC — workspace 정리 후 SQS 메시지 삭제 (재시도 없이 실패 처리)
Error Log#
Expected positive integer for bottom but received -96409 of type number
Impact#
- Service:
cupixworks-any-floorplan-agent - Team: aec-on
- 발생 횟수: 1
- 최초 발생: 2026-05-26T13:24:47.729Z
- 최근 발생: 2026-05-26T13:24:47.729Z
Root Cause Summary#
FloorplanService.tileFloorplan 메서드에서 이미지를 타일링하기 전 sharp의 extend()를 호출하여 이미지를 정사각형으로 패딩하는데, 이때 bottom: maximumPixels - originalHeight 계산에서 originalHeight(161,945px)가 maximumPixels(65,536px)보다 커서 음수(-96,409)가 발생했다. getTileLodFromImage()가 LOD를 MaxTileLODForImage = 8로 캡하여 maximumPixels를 65,536으로 설정하지만, 실제 이미지가 이보다 클 경우에 대한 방어 로직이 없다.
Technical Analysis#
Code Path#
- Entry point:
floorplan-service.ts:51—FloorplanService.run() floorplan-service.ts:81—translateFloorplan()호출.determineTileLod()가 LOD를 최대Highest = 7로 계산하고, GM에 32,768x32,768으로 resize 요청.floorplan-service.ts:104—tileFloorplan()호출. 실제 translated 이미지 파일을 읽어 타일링 시작.floorplan-service.ts:203—getTileLodFromImage()호출. 실제 이미지 dimensions에서 LOD 계산 후MaxTileLODForImage = 8로 cap.- Failure point:
floorplan-service.ts:214—extend({ bottom: maximumPixels - originalHeight })호출 시 음수 값 전달.
const tileLOD = await this.getTileLodFromImage(source, 0);
const maximumPixels = Constants.DefaultTilePixel * Math.pow(2, tileLOD);
// tileLOD는 MaxTileLODForImage=8로 cap → maximumPixels = 65,536
// 그러나 originalHeight가 161,945인 경우...
return new Promise((resolve, reject) => {
sharp(source, sharpOptions)
.png({ force: true })
.extend({
right: maximumPixels - originalWidth,
bottom: maximumPixels - originalHeight, // 65,536 - 161,945 = -96,409 ❌
background: { r: 0, g: 0, b: 0, alpha: 0 }
})
getTileLodFromImage()에서 LOD를 cap하는 로직:
private getTileLodFromImage = async (filePath: string, additionalLevel?: number): Promise<number> => {
const _additionalLevel = additionalLevel == undefined ? 0 : additionalLevel;
const resolution = await this.getImageResolution(filePath);
const width = resolution[0];
const height = resolution[1];
const _tileLOD = Math.ceil(Math.log2(Math.max(width, height) / Constants.DefaultTilePixel));
if (Constants.MaxTileLODForImage > 0
&& _tileLOD + _additionalLevel > Constants.MaxTileLODForImage
) {
return Constants.MaxTileLODForImage; // 8로 cap하지만 실제 이미지는 더 클 수 있음
} else {
return _tileLOD + _additionalLevel;
}
};
translateFloorplan()의 resize 설정:
const tileLod = this.determineTileLod(cpFloorplan);
const width = Constants.DefaultTilePixel * Math.pow(2, tileLod);
const height = Constants.DefaultTilePixel * Math.pow(2, tileLod);
determineTileLod()의 cap 로직 — 최대 Highest = 7 (32,768px):
const tileLODfromResolution = Math.ceil(Math.log2(Math.max(width, height) / Constants.DefaultTilePixel));
if (tileLODfromResolution > tileLOD) {
tileLOD = tileLODfromResolution > Constants.FloorplanTileLOD.Highest ? Constants.FloorplanTileLOD.Highest : tileLODfromResolution;
}
GraphicsMagick resize 명령:
if (width != undefined && height != undefined) {
command += ` -resize ${width}x${height}`;
}
기대 동작: translateFloorplan()이 이미지를 최대 32,768x32,768로 resize한 후 tileFloorplan()이 해당 파일을 읽으면, getTileLodFromImage()는 LOD 7을 반환하고 maximumPixels = 32,768이 되어 extend가 정상 동작해야 한다.
실제 동작: translated 이미지의 높이가 161,945px로 65,536px을 초과했다. GraphicsMagick의 -resize WxH는 aspect ratio를 유지하면서 bounding box에 맞추므로 정상적으로는 32,768을 초과할 수 없다. 이미지가 예상대로 resize되지 않은 경우가 발생한 것으로, GraphicsMagick의 -density 300 플래그가 특정 이미지 포맷(TIFF 등)에서 DPI 해석에 영향을 주어 resize 결과가 의도한 크기를 초과했을 가능성이 있다.
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-any-floorplan-agent status:error @environment:production "Expected positive integer"
service:cupixworks-any-floorplan-agent @environment:production
에러 발생 전후 타임라인 (floorplan 87012):
13:24:18.962 [info] CupixAuth::setSession | session_id: 37aba3fd7797003e030ad2a7f63daedb9a9ace78
13:24:18.996 [info] FloorplanService::run | floorplan state: resource_uploaded, resource_state: uploaded
13:24:47.716 [info] FloorplanService::tileFloorplan | begin
13:24:47.729 [error] Expected positive integer for bottom but received -96409 of type number
13:24:47.730 [info] BaseService::cleanUpAnythingRelatedModel | path: /tmp/workspace/87012
13:24:47.894 [info] AwsQueueManager::deleteMessage | begin
13:24:47.922 [info] AwsQueueManager::deleteMessage | end - message id: 7b7dbb04-8fd2-4785-90de-37b04a43ad97
에러 stack trace:
Error: Expected positive integer for bottom but received -96409 of type number
at Object.invalidParameterError (/tmp/agent/dist/node_modules/.pnpm/sharp@0.33.5/node_modules/sharp/lib/is.js:135:10)
at Sharp.extend (/tmp/agent/dist/node_modules/.pnpm/sharp@0.33.5/node_modules/sharp/lib/resize.js:412:18)
at /tmp/agent/dist/app.cjs:6882:12
at new Promise (<anonymous>)
at FloorplanService.tileFloorplan (/tmp/agent/dist/app.cjs:6879:14)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async FloorplanService.run (/tmp/agent/dist/app.cjs:6775:11)
주요 식별 정보:
- Floorplan ID: 87012
- Team: aec-on (ID: 657)
- User: mvillamor@aec-on.com (ID: 9941)
- SQS Queue:
cupix-tesla-floorplan-agent-production - Host:
ip-10-1-46-206.us-west-2.compute.internal
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | tileFloorplan에서 extend 호출 시 이미지 크기가 maximumPixels를 초과하여 음수 패딩 값 계산 |
에러 메시지 "bottom = -96409" → originalHeight = 161,945 > maximumPixels(65,536). stack trace가 Sharp.extend → FloorplanService.tileFloorplan 경로 확인. |
— | Confirmed |
| H2 | GraphicsMagick translate 단계에서 resize가 정상 작동하지 않아 출력 이미지가 의도한 크기보다 훨씬 큼 | 세션 시작~tileFloorplan 사이 29초 갭은 translate 처리 시간. -density 300이 특정 이미지 포맷에서 resize 결과에 영향을 줄 수 있음. |
GM의 -resize WxH는 일반적으로 bounding box 내로 constraining함. translate 에러 로그 없음. |
Inconclusive |
| H3 | tileFloorplan이 translated 파일이 아닌 원본 다운로드 파일을 읽음 |
run() 메서드 lines 89-96에서 localFilePath가 없으면 downloadUrl에서 재다운로드하는 로직 존재. |
translateFloorplan의 output이 cpFloorplan.localFilePath로 설정되므로 파일이 이미 존재해야 함. fs.existsSync 체크로 보호됨. |
Rejected |
| H4 | getTileLodFromImage의 LOD cap(8)과 determineTileLod의 LOD cap(7) 불일치가 근본 원인 |
determineTileLod는 max 7 (32,768), getTileLodFromImage는 max 8 (65,536). 두 값이 다르면 extend 계산에서 불일치 발생 가능. 그러나 이 경우에도 extend는 양수여야 함 (32,768 이미지 → LOD 7 → maximumPixels 32,768). |
LOD cap 불일치 자체는 에러의 직접 원인이 아님. 이미지가 cap된 maximumPixels보다 클 때만 문제 발생. | Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 파일:
applications/agents/packages/cupix-tesla-floorplan-agent/src/floorplan-service.ts:212-215 extend()호출 전에right와bottom값이 음수인지 검증하고, 음수일 경우 sharp의resize()를 먼저 호출하여 이미지를maximumPixels이내로 축소한 후 extend를 적용해야 한다.- 또는
Math.max(0, maximumPixels - originalWidth)등으로 음수 패딩을 방지하고, 이미지가maximumPixels보다 클 경우 먼저 resize하는 분기를 추가.
단기 개선 (1주 이내)#
tileFloorplan에서getTileLodFromImage결과를 사용할 때, 실제 이미지 dimensions가maximumPixels를 초과하는 경우를 명시적으로 처리하는 로직 추가. 이미지를maximumPixels크기로 resize한 후 extend+tile 진행.translateFloorplan이후 출력 파일의 dimensions를 검증하는 로그 추가 (info 레벨). translate 결과가 예상 크기를 초과하는 경우 warn 로그 출력.
장기 개선 (재발 방지)#
determineTileLod(translate용)와getTileLodFromImage(tile용)의 LOD cap 값을 통일하거나, tile 단계에서 항상 이미지를maximumPixels이내로 보장하는 resize-then-extend 패턴 적용.- GraphicsMagick의
-density플래그가 다양한 이미지 포맷에서 resize 결과에 미치는 영향을 테스트하고, 필요 시 raster 이미지에 대해-density를 제외하거나 resize 결과를 검증하는 로직 추가.
Monitoring#
- 추가할 메트릭:
floorplan.tile.extend_negative_value— extend 계산 결과가 음수인 경우 카운트 - Datadog 알림 쿼리:
service:cupixworks-any-floorplan-agent status:error "Expected positive integer for" @environment:production
- translate 출력 이미지 크기 로그 추가 후 모니터링:
service:cupixworks-any-floorplan-agent "FloorplanService::tileFloorplan" "maximumPixels" @environment:production
Risk Assessment#
- Risk level: low (1건 발생, 특정 이미지 크기 조건에서만 재현)
- 예상 복잡도: trivial (extend 호출 전 음수 체크 및 resize 분기 추가)