{"record":{"id":"bf3bf55545d6749b","repo":"heygen-com/hyperframes","slug":"handler-chunk-uri-at-index-i-is-empty","errorCode":null,"errorMessage":"[handler] chunk URI at index ${i} is empty","messagePattern":"\\[handler\\] chunk URI at index (.+?) is empty","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/aws-lambda/src/handler.ts","lineNumber":752,"sourceCode":"\nasync function downloadChunkObjects(\n  s3: S3Client,\n  uris: string[],\n  workDir: string,\n  format: DistributedFormat,\n): Promise<string[]> {\n  const chunksDir = join(workDir, \"chunks\");\n  mkdirSync(chunksDir, { recursive: true });\n  // Each chunk is an independent S3 GET (+ untar for png-sequence). Run\n  // them in parallel — assemble's wall-clock is otherwise dominated by\n  // `Σ chunk-download-ms` instead of `max(chunk-download-ms)`. Preserve\n  // the input order by writing into a pre-sized array rather than\n  // pushing as each task settles.\n  const local: string[] = new Array<string>(uris.length);\n  await Promise.all(\n    uris.map(async (uri, i) => {\n      if (!uri) {\n        throw new Error(`[handler] chunk URI at index ${i} is empty`);\n      }\n      const { key } = parseS3Uri(uri);\n      const localPath = join(chunksDir, basename(key));\n      await downloadS3ObjectToFile(s3, uri, localPath);\n      if (format === \"png-sequence\") {\n        const dirPath = join(chunksDir, `frames-${pad(i)}`);\n        await untarDirectory(localPath, dirPath);\n        local[i] = dirPath;\n      } else {\n        local[i] = localPath;\n      }\n    }),\n  );\n  return local;\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────\n","sourceCodeStart":734,"sourceCodeEnd":770,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/aws-lambda/src/handler.ts#L734-L770","documentation":"Thrown by `downloadChunks` during the `assemble` action when a chunk URI in the input array is empty (falsy). Each chunk URI must resolve to an S3 object; an empty entry usually means the planner emitted a sparse array or a chunk index was skipped.","triggerScenarios":"`downloadChunks(uris, ...)` with an element that is `\"\"`, `null`, or `undefined` at index `i` — i.e. the assemble event's chunk-URI list is sparse or has a gap.","commonSituations":"Planner bug that built a sparse chunk list when a chunk produced no output; renderChunk result aggregation that left a hole at a failed/empty chunk index; JSON round-trip that dropped an undefined element.","solutions":["Inspect the assemble event's chunk URI array in CloudWatch to locate the gap.","Ensure the planner emits a contiguous, fully-populated URI array (no skipped indices).","Re-run `plan` to regenerate a complete chunk list before `assemble`.","If a chunk legitimately has no output, define the protocol for that slot rather than leaving it empty."],"exampleFix":"// before: sparse chunk list\nconst uris = [\"s3://b/chunk-0\", \"\", \"s3://b/chunk-2\"];\n// after: contiguous, fully populated\nconst uris = [\"s3://b/chunk-0\", \"s3://b/chunk-1\", \"s3://b/chunk-2\"];","handlingStrategy":"validation","validationCode":"function assertChunkUris(uris: unknown[]): void {\n  uris.forEach((uri, i) => {\n    if (typeof uri !== \"string\" || uri.length === 0) {\n      throw new Error(`chunk URI at index ${i} is empty`);\n    }\n  });\n}\n// call before downloadChunks","typeGuard":"function isNonEmptyStringArray(value: unknown): value is string[] {\n  return Array.isArray(value) && value.every((v) => typeof v === \"string\" && v.length > 0);\n}","tryCatchPattern":null,"preventionTips":["Build the chunk URI array with a contiguous `for` loop keyed by index so no slots are skipped.","After plan, assert the chunk list is dense before persisting it.","Log the chunk-URI array length and indices in CloudWatch for assemble events."],"tags":["handler","assemble","chunks","validation","lambda"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}