{"record":{"id":"0e8aaf4ed993e19a","repo":"CherryHQ/cherry-studio","slug":"wechat-can-only-forward-image-files-not-file-m","errorCode":null,"errorMessage":"WeChat can only forward image files, not \"${file.media_type}\" (${file.filename})","messagePattern":"WeChat can only forward image files, not \"(.+?)\" \\((.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/main/ai/channels/adapters/wechat/WeChatAdapter.ts","lineNumber":112,"sourceCode":"    const chunks = splitMessage(text, WECHAT_MAX_LENGTH)\n\n    for (let i = 0; i < chunks.length; i++) {\n      await this.bot.send(chatId, chunks[i])\n\n      if (i < chunks.length - 1) {\n        await new Promise((resolve) => setTimeout(resolve, 100))\n      }\n    }\n  }\n\n  override async sendFile(chatId: string, file: FileAttachment): Promise<void> {\n    if (!this.bot) {\n      throw new Error('Bot is not connected')\n    }\n    // The reverse-engineered WeChat protocol only supports outbound images today\n    // (WeixinBot.sendImage). Document upload would need protocol-level CDN work.\n    if (!file.media_type.startsWith('image/')) {\n      throw new Error(`WeChat can only forward image files, not \"${file.media_type}\" (${file.filename})`)\n    }\n    await this.bot.sendImage(chatId, Buffer.from(file.data, 'base64'))\n    this.log.info('Sent file', { chatId, filename: file.filename, size: file.size })\n  }\n\n  async sendTypingIndicator(chatId: string): Promise<void> {\n    if (!this.bot) {\n      throw new Error('Bot is not connected')\n    }\n\n    try {\n      await this.bot.sendTyping(chatId)\n    } catch {\n      // sendTyping requires a cached context_token from a prior message;\n      // silently ignore if not yet available\n    }\n  }\n","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/channels/adapters/wechat/WeChatAdapter.ts#L94-L130","documentation":"Thrown by WeChatAdapter.sendFile() when file.media_type does not start with 'image/'. This is a hard capability limit: the reverse-engineered WeChat iLink protocol (WeChatProtocol.ts) only implements outbound image upload via cdnUploadImage → WeixinBot.sendImage. There is no document/file upload path — the comment at WeChatAdapter.ts:109 notes 'Document upload would need protocol-level CDN work.' This is an intentional guard, not a transient failure.","triggerScenarios":"sendFile() called with a FileAttachment whose media_type is application/pdf, text/plain, application/zip, video/*, audio/*, etc. The adapter posts images via bot.sendImage (WeChatAdapter.ts:114) which uploads to WeChat CDN with media_type:1 (image) — there is no code path for other types. The check is a startsWith('image/') prefix match, so 'image/png', 'image/jpeg', 'image/gif', 'image/webp' pass.","commonSituations":"The agent forwards a PDF, a code file, or a voice clip to WeChat; a tool output includes a non-image attachment; the agent is configured on multiple channels and a file type that Slack/Telegram accept (documents) is routed to WeChat uniformly.","solutions":["Convert non-image files to images before sending to WeChat (e.g. render a PDF page to PNG), or send a text description/link instead.","Filter outbound attachments by channel capability: route documents only to channels that support them, send a placeholder message to WeChat.","If document upload becomes required, implement the protocol-level CDN file upload in WeChatProtocol.ts (new media_type constant + cdnUploadFile) — the comment marks this as known unfinished work.","Do not retry — this throw is deterministic for a given media_type; fix the input or skip the send."],"exampleFix":"// before — non-image attachment throws and breaks the tool\nawait wechatAdapter.sendFile(chatId, { filename: 'report.pdf', media_type: 'application/pdf', data, size })\n\n// after — branch on channel capability before sending\nconst supportsDocs = (type: string) => !type.startsWith('wechat')\nif (file.media_type.startsWith('image/')) {\n  await adapter.sendFile(chatId, file)\n} else {\n  await adapter.sendMessage(chatId, `[Unsupported on WeChat: ${file.filename}]`)\n}","handlingStrategy":"validation","validationCode":"// Filter by media type BEFORE calling sendFile. WeChat only accepts image/*.\nconst WECHAT_ALLOWED_MEDIA_PREFIX = 'image/'\n\nfunction canWeChatSend(file: FileAttachment): boolean {\n  return file.media_type.startsWith(WECHAT_ALLOWED_MEDIA_PREFIX)\n}\n\n// Usage in a multi-channel dispatcher:\nif (file.media_type.startsWith('image/')) {\n  await wechatAdapter.sendFile(chatId, file)\n} else {\n  await wechatAdapter.sendMessage(chatId, `[Non-image file not supported on WeChat: ${file.filename}]`)\n}","typeGuard":"function isImageAttachment(file: FileAttachment): boolean {\n  return typeof file.media_type === 'string' && file.media_type.startsWith('image/')\n}","tryCatchPattern":"// This is a capability limit, not a transient error — do not retry.\ntry {\n  await wechatAdapter.sendFile(chatId, file)\n} catch (e) {\n  if (e instanceof Error && /WeChat can only forward image files/.test(e.message)) {\n    // Degrade to a text notice or convert the file to an image\n    await wechatAdapter.sendMessage(chatId, `[${file.filename} not supported on WeChat]`)\n    return\n  }\n  throw e\n}","preventionTips":["Maintain a per-channel capability matrix (WeChat: images only; Slack/Telegram: documents) and route attachments accordingly.","Validate media_type at the dispatcher before selecting the adapter, not inside the adapter.","For PDFs/code files, render to PNG before sending to WeChat, or send a download link as text.","Do not retry on this error — it is deterministic for a given media_type."],"tags":["wechat","file-transfer","capability-limit","validation"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}