{"record":{"id":"b3e92e9918880d2e","repo":"CherryHQ/cherry-studio","slug":"feishu-rejected-the-image-upload-for-file-filen","errorCode":null,"errorMessage":"Feishu rejected the image upload for \"${file.filename}\" (no image_key) — likely over Feishu's image size limit (~10MB) or the bot lacks image-send capability","messagePattern":"Feishu rejected the image upload for \"(.+?)\" \\(no image_key\\) — likely over Feishu's image size limit \\(~10MB\\) or the bot lacks image-send capability","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/channels/adapters/feishu/FeishuAdapter.ts","lineNumber":579,"sourceCode":"\n    const buffer = Buffer.from(file.data, 'base64')\n\n    // Images go through the image API so they render inline; everything else is\n    // uploaded as a generic file (`stream`) and delivered as a file message.\n    //\n    // NOTE: unlike `im.message.create`, the SDK's upload endpoints return the\n    // unwrapped data object ({image_key} / {file_key}) — not a {code,msg,data}\n    // envelope — so they must NOT go through `ensureFeishuSuccess`. Two failure\n    // shapes exist: an HTTP error rejects (the http instance surfaces status +\n    // detail), while an HTTP 200 carrying a business error (code != 0, e.g. an\n    // oversize image) resolves to null after the SDK discards code/msg — so for\n    // that path we log and throw an enriched, cause-hinting error ourselves.\n    if (file.media_type.startsWith('image/')) {\n      const uploaded = await this.client.im.image.create({ data: { image_type: 'message', image: buffer } })\n      const imageKey = uploaded?.image_key\n      if (!imageKey) {\n        this.log.warn('Feishu image upload returned no image_key', { chatId, filename: file.filename, size: file.size })\n        throw new Error(\n          `Feishu rejected the image upload for \"${file.filename}\" (no image_key) — likely over Feishu's image size limit (~10MB) or the bot lacks image-send capability`\n        )\n      }\n\n      ensureFeishuSuccess(\n        await this.client.im.message.create({\n          params: { receive_id_type: 'chat_id' },\n          data: { receive_id: chatId, msg_type: 'image', content: JSON.stringify({ image_key: imageKey }) }\n        }),\n        'Send Feishu image'\n      )\n    } else {\n      const uploaded = await this.client.im.file.create({\n        data: { file_type: 'stream', file_name: file.filename, file: buffer }\n      })\n      const fileKey = uploaded?.file_key\n      if (!fileKey) {\n        this.log.warn('Feishu file upload returned no file_key', { chatId, filename: file.filename, size: file.size })","sourceCodeStart":561,"sourceCodeEnd":597,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/channels/adapters/feishu/FeishuAdapter.ts#L561-L597","documentation":"Thrown after a successful-looking im.image.create returns no image_key. Per the in-source comment, upload endpoints return the unwrapped data object (not a {code,msg} envelope), and an HTTP 200 carrying a business error resolves to null after the SDK discards code/msg — so the adapter detects a null image_key and throws an enriched, cause-hinting error itself rather than calling ensureFeishuSuccess.","triggerScenarios":"file.media_type starts with 'image/', client.im.image.create resolves but uploaded.image_key is undefined/null/empty. The adapter logs a warn with chatId/filename/size then throws.","commonSituations":"Image over Feishu's ~10MB image limit, bot lacking the im:message:send_as_bot / image-send capability, corrupted/invalid image bytes, or a transient upload error the SDK swallowed.","solutions":["Compress or downscale the image below 10MB before sendFile; for screenshots, convert PNG to JPEG or resize.","In the Feishu Developer Console, confirm the bot has image-send permission and the app is published/enabled.","Verify file.data base64 decodes to valid image bytes of the declared media_type.","If the cause is transient, retry image upload once before surfacing the error."],"exampleFix":"// before\nif (!imageKey) {\n  this.log.warn('Feishu image upload returned no image_key', { chatId, filename: file.filename, size: file.size })\n  throw new Error(`Feishu rejected the image upload for \"${file.filename}\" (no image_key) ...`)\n}\n\n// after — fall back to a file message so delivery still succeeds\nif (!imageKey) {\n  this.log.warn('Feishu image upload returned no image_key; falling back to file send', { chatId, filename: file.filename, size: file.size })\n  return this.sendFileAsGeneric(chatId, file)\n}","handlingStrategy":"validation","validationCode":"// Validate size before upload.\nfunction isWithinFeishuImageLimit(file: FileAttachment): boolean {\n  const bytes = Math.ceil((file.data.length * 3) / 4) // base64 -> bytes\n  return file.media_type.startsWith('image/') && bytes <= 10 * 1024 * 1024\n}","typeGuard":"function isFeishuImageRejected(e: unknown): e is Error {\n  return e instanceof Error && e.message.startsWith('Feishu rejected the image upload')\n}","tryCatchPattern":"try {\n  await feishu.sendFile(chatId, file)\n} catch (e) {\n  if (isFeishuImageRejected(e)) {\n    await feishu.sendMessage(chatId, `[image too large to upload: ${file.filename}]`)\n    return\n  }\n  throw e\n}","preventionTips":["Downscale/compress images below 10MB before sendFile.","Confirm image-send permission is granted in the Feishu console.","Verify the base64 decodes to valid image bytes of the declared media_type."],"tags":["feishu","image-upload","size-limit","permissions","fallback"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}