santifer/career-ops · error · Error
Unsupported profile photo format: ${photo} (expected PNG, JP
Error message
Unsupported profile photo format: ${photo} (expected PNG, JPEG, WebP, or GIF) What it means
Validation in `prepareCandidatePhoto`: for a local file path (not data URL, not http(s)), the extension must map to an allowed mime via `PHOTO_MIME_BY_EXT`. Only `.png`, `.jpg`/`.jpeg`, `.webp`, `.gif` are accepted; any other extension (or no extension) is rejected before any disk read, so unsupported formats never get embedded into the CV.
Source
Thrown at build-cv-html.mjs:142
throw new Error('Unsupported profile photo data URL (expected base64 PNG, JPEG, WebP, or GIF)');
}
c.photo = photo;
return c;
}
if (/^https?:\/\//i.test(photo)) {
c.photo = photo;
return c;
}
if (/^[a-z][a-z0-9+.-]+:/i.test(photo)) {
throw new Error(`Unsupported profile photo URL scheme: ${photo.split(':', 1)[0]}`);
}
const photoPath = isAbsolute(photo) ? photo : resolve(__dirname, photo);
const mime = PHOTO_MIME_BY_EXT.get(extname(photoPath).toLowerCase());
if (!mime) {
throw new Error(`Unsupported profile photo format: ${photo} (expected PNG, JPEG, WebP, or GIF)`);
}
let bytes;
try {
bytes = await readFile(photoPath);
} catch (err) {
throw new Error(`Profile photo not found or unreadable: ${photo} (${err.code || err.message})`);
}
if (bytes.length === 0) {
throw new Error(`Profile photo is empty: ${photo}`);
}
c.photo = `data:${mime};base64,${bytes.toString('base64')}`;
return c;
}
function joinItems(items) {
if (Array.isArray(items)) return items.join(', ');
return typeof items === 'string' ? items : '';View on GitHub (pinned to 9b17a8ac97)
Solutions
- Convert the photo to PNG, JPEG, WebP, or GIF and use the matching extension.
- Rename mislabeled files (`.jpgg` → `.jpg`) — but only if the bytes actually match.
- For SVG/AVIF/HEIC, rasterize to PNG/WebP first with an image tool (`cwebp`, `convert`, or browser canvas).
- If you genuinely need a new format, extend `PHOTO_MIME_BY_EXT` and ensure the template/CSS handle it.
- Validate upstream: `if (!/\.(png|jpe?g|webp|gif)$/i.test(photo)) throw ...`.
Example fix
# before photo: 'avatar.svg' # throws # after — rasterize once convert avatar.svg avatar.png # then in config photo: 'avatar.png'
Defensive patterns
Strategy: validation
Validate before calling
const ALLOWED = /^(.+\.(png|jpe?g|webp|gif))$/i;
function validPhotoFile(p) {
if (!ALLOWED.test(String(p))) throw new Error('photo must be .png/.jpg/.jpeg/.webp/.gif');
return p;
} Type guard
function isAcceptablePhotoFile(p) {
return /\.(png|jpe?g|webp|gif)$/i.test(String(p ?? ''));
} Prevention
- Convert avatars to PNG/JPEG/WebP/GIF before referencing them.
- Reject SVG/BMP/TIFF/AVIF/HEIC at the upload boundary.
- Rasterize SVG with `cwebp`/`convert`/canvas before embedding.
- Extend PHOTO_MIME_BY_EXT intentionally if a new format is required.
When it happens
Trigger: Passing `photo: 'avatar.svg'`, `'photo.bmp'`, `'me.tiff'`, `'avatar.avif'`, `'photo'` (no extension), `'photo.PDF'`, or `'icon.ico'`. The `extname` is lowercased and looked up in the mime map; misses throw this error.
Common situations: User has an SVG/BMP/TIFF/AVIF/HEIC avatar; file with no extension; wrong extension (`.pdf`, `.heic` from a phone); case differences are handled but the extension itself must be in the set.
Related errors
- Unsupported profile photo style: ${style} (expected rounded,
- Unsupported profile photo data URL (expected base64 PNG, JPE
- Unsupported profile photo URL scheme: ${photo.split(':', 1)[
- Unsupported image type: ${extname(inputPath) || '(no extensi
- payload must include at least one of: cv, articleDigest
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/726f4811c24edd9e.
Report an issue: GitHub.