{"record":{"id":"924e4d175536d996","repo":"NousResearch/hermes-agent","slug":"clipboard-image-is-empty","errorCode":null,"errorMessage":"clipboard image is empty","messagePattern":"clipboard image is empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"web/src/lib/chatImagePaste.ts","lineNumber":126,"sourceCode":"    reader.readAsDataURL(file);\n  });\n}\n\n/**\n * Upload a browser clipboard/drop image to ``HERMES_HOME/images`` via the\n * dedicated chat upload endpoint and return the absolute gateway path.\n *\n * The dashboard Chat tab is an xterm mirror of a TUI running INSIDE the\n * gateway. The container has no access to the browser's clipboard, so the\n * server-side ``clipboard.paste`` path can never see a pasted image.\n * Upload the bytes the browser already holds, then hand the path to the\n * TUI's ``/image`` command.\n */\nexport async function uploadChatImage(\n  blob: Blob,\n  profile = \"\",\n): Promise<ChatImageUploadResult> {\n  if (blob.size === 0) throw new Error(\"clipboard image is empty\");\n  if (blob.size > MAX_IMAGE_BYTES) {\n    const mb = Math.round(MAX_IMAGE_BYTES / (1024 * 1024));\n    throw new Error(`image too large (max ${mb} MB)`);\n  }\n\n  const mime = blob.type || \"image/png\";\n  const ext = IMAGE_MIME_EXT[mime] || \"png\";\n  const filename =\n    blob instanceof File && blob.name\n      ? blob.name\n      : `clipboard.${ext}`;\n  const file =\n    blob instanceof File\n      ? blob\n      : new File([blob], filename, { type: mime });\n\n  const dataUrl = await fileToDataUrl(file);\n  const qs = profile ? `?profile=${encodeURIComponent(profile)}` : \"\";","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/web/src/lib/chatImagePaste.ts#L108-L144","documentation":"uploadChatImage rejects with this when the pasted clipboard Blob has size 0. Because the dashboard Chat tab is an xterm mirror of a TUI inside the gateway, images must be uploaded from the browser's own clipboard bytes; an empty blob means the paste event carried no actual image data.","triggerScenarios":"A paste handler constructs a Blob from an empty clipboard item, or the OS clipboard contains an image entry with zero bytes (e.g. a screenshot tool that failed mid-capture, or a virtualized environment with a broken clipboard channel).","commonSituations":"Pasting from remote-desktop/RDP sessions with clipboard redirection issues, pasting immediately after a failed screenshot, or programmatic paste dispatch in tests with no real clipboard payload.","solutions":["Re-copy the image (retake the screenshot) and paste again — confirm the source app actually placed image bytes on the clipboard.","If this recurs in RDP/VM environments, verify clipboard sharing is enabled; or save the image to a file and attach it instead of pasting.","In code, skip the upload call when event.clipboardData contains no non-empty image item."],"exampleFix":"// before\nconst blob = new Blob([], { type: 'image/png' })\nawait uploadChatImage(blob)\n\n// after\nif (blob.size === 0) return  // nothing to upload\nawait uploadChatImage(blob)","handlingStrategy":"validation","validationCode":"function extractPasteImage(items: DataTransferItemList): Blob | null {\n  for (const item of items) {\n    if (item.kind === 'file' && item.type.startsWith('image/')) {\n      const blob = item.getAsFile()\n      if (blob && blob.size > 0) return blob\n    }\n  }\n  return null // nothing usable — skip upload entirely\n}","typeGuard":"const isNonEmptyImage = (b: Blob | null): b is Blob =>\n  !!b && b.size > 0 && b.type.startsWith('image/')","tryCatchPattern":"try {\n  await uploadChatImage(blob)\n} catch (err) {\n  if (String(err).includes('empty')) { toast('Nothing on the clipboard — re-copy the image'); return }\n  throw err\n}","preventionTips":["Check blob.size > 0 before calling uploadChatImage.","Filter clipboard items by kind==='file' and image/* mime.","In RDP/VM setups, verify clipboard redirection works before blaming the app."],"tags":["clipboard","upload","image","browser"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}