Hmbown/CodeWhale · error · ExecError
malformed JPEG at byte
Error message
malformed JPEG at byte ${at}: ${file} What it means
While walking JPEG segment markers to find the SOFn frame header (which carries width/height), `imagePixels` expects every segment to begin with the 0xFF marker byte. If the byte at the current offset is not 0xFF, the file is not a well-formed JPEG and `malformed JPEG at byte <at>: <file>` is thrown.
Solutions
- Re-capture or re-copy the screenshot; the file is corrupt.
- Wait for the capture process to finish writing before reading dimensions.
- Verify the JPEG with a decoder (e.g. `sips -g pixelWidth`) to confirm corruption.
- Add a readiness check (file size stable across two stats) before reading.
Example fix
// before\nimagePixels(shotPath); // file may still be flushing\n// after\nawait waitForFileSettled(shotPath);\nimagePixels(shotPath);
Defensive patterns
Strategy: fallback
Validate before calling
// confirm the file is settled before parsing\nconst s1 = fs.statSync(file).size;\nawait sleep(150);\nif (fs.statSync(file).size !== s1) throw new Error("file still being written"); Try / catch
try { return imagePixels(file); } catch (e) { if (/malformed JPEG/.test(e.message)) { await recaptureScreenshot(file); return imagePixels(file); } throw e; } Prevention
- Ensure capture completes (process exit or size stability) before reading.
- Write screenshots atomically (temp file + rename).
- Periodically verify captured files decode with sips.
When it happens
Trigger: A file that starts with the JPEG SOI magic (0xFF 0xD8) but is corrupted or truncated afterwards — mid-stream corruption, a partially written screenshot, or appended/truncated data shifting segment lengths.
Common situations: Disk-full during capture leaving a truncated JPEG, a file still being written by the capture process when dimensions are read, or bit-flips from a bad transfer.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- JPEG carries no frame header
- unrecognized raster format
- Codewhale-owned xAI OAuth file
- image base64 is not canonical
- image dimensions exceed the decompression bomb guard…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/84cf380c0dc79fc0.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/darwin.mjs:49
* 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);
}
}
/**
* Shrink a raster until it fits the single-message budget.
*
* A 5K display captures to ~22MB of PNG, which is ~29MB of base64 — past every
* host limit, so the alternative is handing back a receipt with no picture andView on GitHub (pinned to 73e0f67d83)