{"record":{"id":"cbbe33de55357574","repo":"Hmbown/CodeWhale","slug":"screenshot-path-must-end-in-png-jpg-or-jpeg","errorCode":null,"errorMessage":"screenshot path must end in .png, .jpg or .jpeg","messagePattern":"screenshot path must end in \\.png, \\.jpg or \\.jpeg","errorType":"exception","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/darwin.mjs","lineNumber":519,"sourceCode":"\n  // ---------- screenshots ----------\n  function recordingsDir() {\n    return process.env.CODEWHALE_CU_RECORDINGS_DIR || path.join(os.homedir(), \".codewhale-cu\", \"recordings\");\n  }\n\n  async function screenshot({ display, region, app_ref, window_id, path: outPath } = {}) {\n    // Once an app is selected, ordinary observations follow it behind the\n    // user's work. An explicit display/region remains a deliberate desktop capture.\n    if (app_ref === undefined && display === undefined && region === undefined) app_ref = state.inputApp ?? undefined;\n    const dir = recordingsDir();\n    fs.mkdirSync(dir, { recursive: true });\n    // JPEG, not PNG. A screen is photographic content — gradients, wallpaper,\n    // antialiased text — and lossless compression of it is enormous: the same\n    // 5760x3240 frame is 21.8MB as PNG and 2.1MB as JPEG, at full resolution\n    // and with terminal text still crisp. PNG stays available by asking for a\n    // `.png` path, which is what a pixel-exact comparison wants.\n    const file = outPath || path.join(dir, `shot-${new Date().toISOString().replace(/[:.]/g, \"-\")}-${crypto.randomBytes(3).toString(\"hex\")}.jpg`);\n    if (!/\\.(png|jpe?g)$/i.test(file)) throw new ExecError(\"screenshot path must end in .png, .jpg or .jpeg\");\n    const args = [\"-x\", \"-t\", /\\.png$/i.test(file) ? \"png\" : \"jpg\"];\n    const disp = display ?? state.activeDisplay;\n    // An explicit app reference resolves first and alone: nothing may run\n    // before it and redirect the capture to another target.\n    const window = app_ref !== undefined ? await native(\"window_info\", { app_ref, window_id }) : null;\n    if (window && region) throw new ExecError(\"choose app_ref or region, not both\");\n    // On the display path, resolve displays before capturing so an unknown\n    // index is a clean error instead of a raster silently labelled with another\n    // display's geometry — list_displays reports `index` and `id` separately,\n    // and a caller passing the id would otherwise get points and scale that\n    // mis-target every later coordinate. A window capture ignores `display`.\n    let displays = null;\n    if (!window) {\n      displays = await displayInfo();\n      if (disp != null && disp !== \"all\" && !displays.some((x) => x.index === disp)) {\n        throw new ExecError(`no display ${disp}; have [${displays.map((x) => x.index).join(\", \")}] — screenshot takes the display index from list_displays, not its id`);\n      }\n    }","sourceCodeStart":501,"sourceCodeEnd":537,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/darwin.mjs#L501-L537","documentation":"The screenshot API validates that the requested output path has a .png, .jpg or .jpeg extension (case-insensitive). The extension also selects the capture format via the underlying screencapture tool, so an unknown extension cannot be mapped to a format.","triggerScenarios":"Calling screenshot with outPath like 'shot.bmp', 'shot.txt', 'shot' (no extension), 'shot.tiff', or a path with a trailing character after the extension.","commonSituations":"Caller picking an image format the backend does not support (bmp/webp); building filenames from templates that append suffixes; forgetting the extension entirely.","solutions":["End the path in .jpg/.jpeg (preferred: much smaller for photographic screen content) or .png for lossless","Convert the captured file to another format afterwards with a separate tool if another format is needed","Omit outPath entirely to get an auto-generated timestamped .jpg path","Fix filename templates that append unsupported suffixes"],"exampleFix":"// before\nawait screenshot({ outPath: \"shot.bmp\" });\n// after\nawait screenshot({ outPath: \"shot.jpg\" });","handlingStrategy":"validation","validationCode":"if (!/\\.(png|jpe?g)$/i.test(outPath)) throw new Error(`unsupported screenshot format: ${outPath}; use .png, .jpg or .jpeg`);","typeGuard":null,"tryCatchPattern":"try {\n  await screenshot({ outPath });\n} catch (e) {\n  if (e instanceof ExecError && e.message.includes(\"must end in .png\")) {\n    await screenshot({ outPath: outPath.replace(/\\.\\w+$/, \"\") + \".jpg\" });\n  } else throw e;\n}","preventionTips":["Prefer .jpg for screenshots (much smaller); reserve .png for pixel-exact comparisons","Validate extensions in one shared filename helper","Let the backend generate its timestamped path when the name does not matter"],"tags":["screenshot","file-extension","validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}