Hmbown/CodeWhale · error · ExecError
JPEG carries no frame header
Error message
JPEG carries no frame header: ${file} What it means
After walking all JPEG segments without encountering an SOFn frame header (0xC0–0xCF excluding DHT/C8/CC variants), `imagePixels` gives up with `JPEG carries no frame header: <file>`. A valid baseline or progressive JPEG must contain exactly one such frame header, so its absence means dimensions cannot be determined.
Solutions
- Re-capture the screenshot; this file has no decodable frame header.
- Validate with an image tool (`sips -g pixelHeight file`) before using imagePixels.
- Fall back to capturing dimensions from the capture command output instead of parsing the file.
- Check the writer that produced the file — a short write likely truncated it before the frame header.
Example fix
// before\nconst { w, h } = imagePixels(path); // throws if SOF missing\n// after\ntry { const { w, h } = imagePixels(path); }\ncatch (e) { const { width, height } = dimsViaSips(path); } Defensive patterns
Strategy: fallback
Validate before calling
const out = childProcess.execFileSync("sips", ["-g", "pixelWidth", "-g", "pixelHeight", file], {encoding: "utf8"});\nif (!/pixelWidth:\s*\d+/.test(out)) throw new Error("undecodable image: " + file); Try / catch
try { return imagePixels(file); } catch (e) { if (/no frame header/.test(e.message)) return dimsViaSips(file); throw e; } Prevention
- Validate screenshots decode before relying on parsed dimensions.
- Prefer PNG captures, which have a simple fixed header layout.
- Detect truncation by comparing file size to the encoder's expected output.
When it happens
Trigger: A JPEG missing its SOF marker — heavily truncated files where the data ends before the frame header is reached, or non-image data that happens to begin with 0xFF 0xD8, or a file whose segment lengths are wrong so the walk terminates early.
Common situations: Zero-dimension or degenerate capture output, files that pass the two-byte magic sniff but are not real JPEGs, or truncated downloads/recordings used as screenshots.
Related errors
- malformed JPEG at byte
- 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/210729a573992e7e.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/darwin.mjs:57
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 and
* a screenshot tool that never shows anything. Downscaling here, before the
* caller reads the PNG header, keeps coordinates exact by construction:
* `pixels` comes from the header, `points` stays in screen points, and `scale`
* is derived from the two, so raster-to-point conversion follows automatically.
*
* PNG bytes track pixel count, so the long edge shrinks by the square root of
* the overshoot. The estimate is verified rather than trusted — screen content
* compresses unevenly — and gives up rather than shrinking past legibility.View on GitHub (pinned to 73e0f67d83)