"My AI Assistant Could See the Screenshot. Feishu Still Got No Image" A developer working on CliGate, a local control plane for AI assistants, discovered a bug where Feishu and Telegram failed to receive images even though the assistant confirmed sending them. The root cause was a capability check in the assistant tool layer that silently stripped image payloads before they reached the providers. The fix required aligning capability flags across layers and implementing local-first image upload logic. The most confusing delivery bug is not when an image upload fails loudly. It is when the assistant can clearly see the screenshot, tells you it is sending it, and the chat still gets no image. That is the bug I ran into while working on CliGate https://github.com/codeking-ai/cligate , my local control plane for a resident assistant, channel workflows, desktop automation, and AI tool routing. The screenshot existed. The assistant had the artifact. DingTalk could already receive images. But Feishu and Telegram still behaved like the image had vanished somewhere between "I have it" and "I sent it." The obvious place to look was the channel provider layer. That made sense at first. Feishu needs image bytes uploaded first so it can return an image key . Telegram can accept a file upload through sendPhoto . So the initial assumption was simple: feishu-provider.js telegram-provider.js But that was only half the path. The real outbound flow looked more like this: php assistant tool - delivery sender - provider - channel chat And the bug was hiding earlier than I expected. CliGate has an assistant tool called send message to channel . That tool decides whether a channel is image-capable before it forwards payloads into the delivery path. The critical line was basically this: js const effectiveImages = imageSupported ? images : ; That sounds harmless until your capability map is wrong. In my case, the tool-level allowlist still treated only DingTalk as image-capable. So even if I taught Feishu and Telegram providers how to upload and send images, the assistant tool was silently stripping the image array before the providers ever received it. That is why this kind of bug is so misleading. The model is not hallucinating the screenshot. The provider is not necessarily broken. The payload is being amputated one layer upstream. There was a second detail that mattered. CliGate runs locally on localhost , and assistant-generated images can exist as: data: URL localhost URLThat matters because Feishu and Telegram do not treat image inputs the same way. Feishu wants uploaded bytes first, then an image key . Telegram can fetch some public URLs remotely, but a localhost URL is useless to Telegram's servers. So "just pass the URL through" is not a reliable local-first strategy. The safer rule became: php resolve image bytes locally - upload from CliGate itself - send the provider-specific image payload That means: image key , send image message sendPhoto In other words, the fix was not only "mark these channels as image-capable." It was also "make the provider implementation match local-first reality." The lesson was that channel capability is not one boolean in one file. It is an agreement across layers. For this bug, the useful fix had three parts: Once those lined up, the behavior stopped being weird. If the assistant has an image artifact, the delivery layer now keeps it intact long enough for the right provider to do the right thing. That sounds small, but it changes the user experience a lot. A screenshot is usually the proof that the task really happened. If the text says "I sent the screenshot" but the image never arrives, trust drops immediately. When an agent sends files, screenshots, or generated images into chat, do not debug only the provider. Trace the whole delivery path: That is now one of my standing checks for CliGate. The bug looked like "Feishu and Telegram cannot send images." The real problem was subtler: the assistant pipeline and the provider capabilities had drifted apart. The project is open source here: CliGate on GitHub https://github.com/codeking-ai/cligate . If you are building local agents with chat delivery, where would you put the capability check: in the tool layer, the provider, or both?