Hmbown/CodeWhale · error · ExecError
unrecognized raster format
Error message
unrecognized raster format: ${file} What it means
`imagePixels` in the darwin backend sniffs the first bytes of a screenshot file to read image dimensions. It recognizes PNG (0x89 'PNG') and JPEG (0xFF 0xD8) magic numbers; anything else raises `unrecognized raster format: <file>`. It only reads the header, so it supports exactly these two formats.
Solutions
- Ensure the capture pipeline writes PNG (default) or JPEG, e.g. `screencapture -t png <file>`.
- Verify the file exists and is non-empty before calling imagePixels.
- Convert other formats to PNG/JPEG before dimension lookup.
- Check the path — the file may be an HTML error page or placeholder, not the screenshot.
Example fix
// before\nexec("screencapture -t tiff /tmp/shot.tif");\nimagePixels("/tmp/shot.tif"); // throws\n// after\nexec("screencapture -t png /tmp/shot.png");\nimagePixels("/tmp/shot.png"); Defensive patterns
Strategy: fallback
Validate before calling
const fd = fs.openSync(file, "r");\nconst head = Buffer.alloc(2); fs.readSync(fd, head, 0, 2, 0); fs.closeSync(fd);\nconst isPng = head[0] === 0x89, isJpeg = head[0] === 0xff && head[1] === 0xd8;\nif (!isPng && !isJpeg) throw new Error(`${file} is not PNG/JPEG`); Type guard
const isSupportedRaster = (buf) => buf[0] === 0x89 || (buf[0] === 0xff && buf[1] === 0xd8);
Try / catch
try { return imagePixels(file); } catch (e) { if (/unrecognized raster format/.test(e.message)) return dimsViaSips(file); throw e; } Prevention
- Always capture with screencapture's default png format.
- Stat the file and check size > 0 before parsing.
- Convert non-PNG/JPEG screenshots with sips before dimension lookup.
When it happens
Trigger: Calling imagePixels (screenshot dimension lookup) on a file that is not PNG or JPEG — e.g. a TIFF screenshot from an older macOS capture path, a WebP/HEIC image, an empty file, or a text/HTML placeholder written where an image was expected.
Common situations: macOS screencapture configured for a different format (screencapture -t tiff), a truncated or zero-byte file whose header is unreadable, or a path pointing at the wrong file.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- JPEG carries no frame header
- malformed JPEG at byte
- choose app_ref or region, not both
- exited
- display index must be a positive integer
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/c7a36a6d87520976.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/darwin.mjs:42
* CODEWHALE_CU_MAX_IMAGE_BYTES in mcp/server.mjs, which keeps the hard guard.
*/
const rasterByteBudget = () => (Number(process.env.CODEWHALE_CU_MAX_IMAGE_BYTES) > 0
? Number(process.env.CODEWHALE_CU_MAX_IMAGE_BYTES)
: 5_000_000);
/**
* Pixel dimensions from a PNG IHDR or a JPEG frame header, reading only the
* bytes that carry them rather than the whole raster.
*/
function imagePixels(file) {
const fd = fs.openSync(file, "r");
try {
const head = Buffer.alloc(24);
fs.readSync(fd, head, 0, 24, 0);
if (head[0] === 0x89 && head.toString("ascii", 1, 4) === "PNG") {
return { w: head.readUInt32BE(16), h: head.readUInt32BE(20) };
}
if (head[0] !== 0xff || head[1] !== 0xd8) throw new ExecError(`unrecognized raster format: ${file}`);
// Walk JPEG segments to the frame header. SOF0/1/2/3/5..7/9..11/13..15
// carry the dimensions; DHT/DQT and the rest are skipped by their length.
const size = fs.fstatSync(fd).size;
const seg = Buffer.alloc(9);
for (let at = 2; at + 9 <= size; ) {
fs.readSync(fd, seg, 0, 9, at);
if (seg[0] !== 0xff) throw new ExecError(`malformed JPEG at byte ${at}: ${file}`);
const marker = seg[1];
if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {
return { w: seg.readUInt16BE(7), h: seg.readUInt16BE(5) };
}
if (marker === 0xd8 || (marker >= 0xd0 && marker <= 0xd9)) { at += 2; continue; }
at += 2 + seg.readUInt16BE(2);
}
throw new ExecError(`JPEG carries no frame header: ${file}`);
} finally {
fs.closeSync(fd);
}View on GitHub (pinned to 73e0f67d83)