nexu-io/open-design · warning · Error

${task.folder}: spritesheet too small (${bytes.length} bytes

Error message

${task.folder}: spritesheet too small (${bytes.length} bytes)

What it means

Thrown by writePet when the downloaded spritesheet binary is smaller than 16 bytes. This is a sanity guard: no valid webp/png/gif image is under 16 bytes. A tiny payload almost always means the URL returned an error page, a redirect HTML snippet, or an empty response that passed the resp.ok check but isn't a real image. The error includes the folder name and byte count.

Source

Thrown at apps/daemon/src/community-pets-sync.ts:206

  const ab = await resp.arrayBuffer();
  return Buffer.from(ab);
}

async function writePet(
  task: PetTask,
  outRoot: string,
  force: boolean,
): Promise<'wrote' | 'skipped'> {
  const dir = path.join(outRoot, task.folder);
  const sheetPath = path.join(dir, `spritesheet.${task.spritesheetExt}`);
  const manifestPath = path.join(dir, 'pet.json');
  if (!force && (await pathExists(sheetPath)) && (await pathExists(manifestPath))) {
    return 'skipped';
  }
  await mkdir(dir, { recursive: true });
  const bytes = await downloadBinary(task.spritesheetUrl);
  if (bytes.length < 16) {
    throw new Error(`${task.folder}: spritesheet too small (${bytes.length} bytes)`);
  }
  // Reject HTML error pages dressed as `.webp` so the UI doesn't end up
  // adopting a pet whose sprite is `<!doctype html>`.
  const head = bytes.subarray(0, 12);
  const isWebp = head.toString('ascii', 0, 4) === 'RIFF' && head.toString('ascii', 8, 12) === 'WEBP';
  const isPng = head.toString('hex', 0, 8) === '89504e470d0a1a0a';
  const isGif = head.toString('ascii', 0, 6) === 'GIF87a' || head.toString('ascii', 0, 6) === 'GIF89a';
  if (!isWebp && !isPng && !isGif) {
    throw new Error(`${task.folder}: spritesheet is not webp/png/gif`);
  }
  await writeFile(sheetPath, bytes);
  await writeFile(manifestPath, JSON.stringify(task.manifest, null, 2) + '\n', 'utf8');
  return 'wrote';
}

async function runPool<T, R>(
  items: T[],
  concurrency: number,

View on GitHub (pinned to 5be4028344)

Solutions

  1. Manually open the spritesheetUrl in a browser to verify it serves a real image.
  2. Check if the hosting service changed its response format or the asset was corrupted.
  3. Retry the download — transient partial responses sometimes resolve.
  4. If the asset is permanently broken, mark the pet as failed and exclude it from future syncs.
Defensive patterns

Strategy: validation

Validate before calling

// After download, validate size before writing.
function validateSpritesheetSize(bytes: Buffer, folder: string): void {
  const MIN_SIZE = 16;
  if (bytes.length < MIN_SIZE) {
    throw new Error(
      `${folder}: spritesheet too small (${bytes.length} bytes). ` +
      `The URL likely returned an error page or empty response.`
    );
  }
}

// Use before writePet's internal check, or to pre-filter tasks
const bytes = await downloadBinary(url);
validateSpritesheetSize(bytes, folder);

Type guard

export function isSpritesheetTooSmallError(error: unknown): boolean {
  return error instanceof Error && error.message.includes('spritesheet too small');
}

Try / catch

try {
  await writePet(task, outRoot, force);
} catch (error) {
  if (error instanceof Error && error.message.includes('spritesheet too small')) {
    // Skip this pet — the asset is broken on the remote end
    syncResult.failed++;
    syncResult.errors.push(error.message);
    continue; // move to next pet
  } else throw error;
}

Prevention

When it happens

Trigger: downloadBinary succeeds (HTTP 200) but the response body is tiny — e.g., a 0-byte response, a truncated file, or a short HTML error string. The bytes.length < 16 check at line 207 fires.

Common situations: The CDN returns HTTP 200 with an empty or error body. A proxy strips the image content. The spritesheet URL points to a placeholder/redirect page. A Supabase storage bucket returns a tiny JSON error with 200 status.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/eab573d205bb01e4. Report an issue: GitHub.