moeru-ai/airi · error · Error
Screenshot must be a file smaller than 8 MiB.
Error message
Screenshot must be a file smaller than 8 MiB.
What it means
readImage() enforces a hard 8 MiB size limit (stat().size) and requires the target to be a regular file before base64-encoding it into a data URL. Oversized files are rejected to keep the in-memory base64 payload and the model context bounded.
Solutions
- Re-capture with a smaller region or use JPEG output to reduce size
- Check file size with fs.stat before calling readImage and downscale/compress if > 8 MiB
- Ensure the path is the actual screenshot file, not a directory
Example fix
// before
if ((await stat(p)).size > 8 * 1024 * 1024) { /* will throw */ }
await readImage({ path: p })
// after
const info = await stat(p)
if (info.size > 8 * 1024 * 1024) p = await reencodeAsJpeg(p)
await readImage({ path: p }) Defensive patterns
Strategy: validation
Validate before calling
import { stat } from 'node:fs/promises'
async function isReadableScreenshotSize(p: string): Promise<boolean> {
const info = await stat(p)
return info.isFile() && info.size <= 8 * 1024 * 1024
} Try / catch
try {
return await readImage({ path })
} catch (e) {
if (e instanceof Error && e.message.includes('smaller than 8 MiB')) {
const shrunk = await compressImage(path)
return readImage({ path: shrunk })
}
throw e
} Prevention
- Check stat().size before calling readImage and compress/downscale when near 8 MiB
- Prefer JPEG output for large multi-monitor captures
- Ensure the path is a regular file, not a directory
When it happens
Trigger: stat() reports a file larger than 8 * 1024 * 1024 bytes, or the path resolves to a directory/FIFO rather than a regular file.
Common situations: High-resolution multi-monitor captures saved as uncompressed PNG; reading a raw/bitmap dump instead of the CLI's encoded screenshot; pointing at a directory inside the store.
Understand the failure class
Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.
Related errors
AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17).
Data as JSON: /api/errors/79425a80393c19ef.
Report an issue: GitHub.
Appendix: source
Thrown at apps/stage-tamagotchi/src/main/services/airi/computer-use/runtime.ts:146
}
catch {
// Help output is text. Keep it intact for command discovery.
}
return { argv, exitCode, output, stderr }
})
}
async function readImage(input: unknown): Promise<string> {
const { path } = v.parse(v.object({ path: v.string() }), input)
return enqueue(async () => {
const root = await realpath(options.storeRoot)
const target = await realpath(path)
const within = relative(root, target)
if (!within || isAbsolute(within) || within === '..' || within.startsWith(`..${sep}`))
throw new Error('Only screenshots from this AIRI computer-use store can be read.')
const info = await stat(target)
if (!info.isFile() || info.size > 8 * 1024 * 1024)
throw new Error('Screenshot must be a file smaller than 8 MiB.')
const data = await readFile(target)
const png = data.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))
const jpeg = data[0] === 255 && data[1] === 216 && data[2] === 255
if (!png && !jpeg)
throw new Error('Screenshot must be PNG or JPEG.')
return `data:image/${png ? 'png' : 'jpeg'};base64,${data.toString('base64')}`
})
}
/** Rejects queued calls, aborts active work, and waits for the owned daemon to exit. */
async function dispose() {
disposed = true
abort.abort()
await queue
try {
await daemon?.stop()
}
finally {View on GitHub (pinned to 438a067dde)