Skip to content

Commit 8959c81

Browse files
committed
fix(ai): crash when sending clipboard-pasted images in AI chat
DataTransferItem.type becomes invalid after the paste event handler returns, but the code read it inside an async FileReader.onload callback, resulting in an empty media_type that crashed the Claude Code subprocess (exit code 1). - Capture mediaType from blob.type synchronously before FileReader - Add server-side fallback: infer media type from base64 magic bytes - Add stderr logging for Claude Code subprocess diagnostics
1 parent bc17f18 commit 8959c81

2 files changed

Lines changed: 25 additions & 3 deletions

File tree

src-node/claude-code-agent.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale,
318318
const queryOptions = {
319319
cwd: projectPath || process.cwd(),
320320
maxTurns: undefined,
321+
stderr: (data) => console.log("[AI stderr]", data),
321322
allowedTools: [
322323
"Read", "Edit", "Write", "Glob", "Grep", "Bash",
323324
"AskUserQuestion", "Task",
@@ -584,10 +585,27 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale,
584585
let sdkPrompt = prompt;
585586
if (images && images.length > 0) {
586587
const contentBlocks = [{ type: "text", text: prompt }];
587-
images.forEach(function (img) {
588+
images.forEach(function (img, idx) {
589+
// Infer media type from base64 header if missing
590+
let mediaType = img.mediaType;
591+
if (!mediaType && img.base64Data) {
592+
if (img.base64Data.startsWith("iVBOR")) {
593+
mediaType = "image/png";
594+
} else if (img.base64Data.startsWith("/9j/")) {
595+
mediaType = "image/jpeg";
596+
} else if (img.base64Data.startsWith("R0lGOD")) {
597+
mediaType = "image/gif";
598+
} else if (img.base64Data.startsWith("UklGR")) {
599+
mediaType = "image/webp";
600+
} else {
601+
mediaType = "image/png";
602+
}
603+
}
604+
_log("Image[" + idx + "]:", "mediaType=" + mediaType,
605+
"base64Len=" + (img.base64Data ? img.base64Data.length : "null"));
588606
contentBlocks.push({
589607
type: "image",
590-
source: { type: "base64", media_type: img.mediaType, data: img.base64Data }
608+
source: { type: "base64", media_type: mediaType, data: img.base64Data }
591609
});
592610
});
593611
sdkPrompt = (async function* () {

src/core-ai/AIChatPanel.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,10 @@ define(function (require, exports, module) {
443443
}
444444
imageFound = true;
445445
const blob = item.getAsFile();
446+
// Capture mediaType synchronously — DataTransferItem properties
447+
// become invalid after the paste event handler returns, but
448+
// the File object's type persists across the async boundary.
449+
const mediaType = blob.type || item.type;
446450
const reader = new FileReader();
447451
reader.onload = function (ev) {
448452
const dataUrl = ev.target.result;
@@ -454,7 +458,7 @@ define(function (require, exports, module) {
454458
_addImageIfUnique(resized.dataUrl, resized.mediaType, resized.base64Data);
455459
});
456460
} else {
457-
_addImageIfUnique(dataUrl, item.type, base64Data);
461+
_addImageIfUnique(dataUrl, mediaType, base64Data);
458462
}
459463
};
460464
reader.readAsDataURL(blob);

0 commit comments

Comments
 (0)