2026-06-22 OWUI Images Rendering Twice

Context: Working on dotfiles (.config, branch main).

What I set out to do

Figure out why generated images were displaying twice in Open WebUI, using two recent chats as evidence: “🏗️ OpenRouter LiteLLM Stack” and ”🍓 Strawberry Bed Build Infographic”.

What I actually did

Read the actual stored chats out of webui.db (the chat table, parsing history.messages.*) rather than theorizing. Found the image was registered through two channels that OWUI never dedupes:

  • OpenRouter chat — the generate_image builtin tool attaches the image to message.files and returns its /api/v1/files/<id>/content url to the model with a “don’t embed again” instruction. The model (DeepSeek V4 Flash orchestrator) ignored that and re-embedded it as markdown → attachment + markdown = twice.
  • Strawberry chat (gpt-5-image, inline delta.images) — the same file id landed in message.files twice. add_message_files_by_id_and_message_id does message_files + files with no dedup (models/chats.py), and upload_file_handler mints a fresh id per upload, so a repeated id means the same entry was appended twice.

Wrote two fixes into the existing boot-time patcher owui_image_cost_patch.py: strip the url from the two builtin emitter-returns (model can’t paste back what it never sees), and dedupe the files list by url. Dry-ran the patcher against the live container, hm switch (recreated the container, all 9 patches re-applied clean), just check green, committed as 2288145.

Then ran a live test through the browser: asked DeepSeek to generate a red circle. The tool fired (“Explored generate_image”), the model replied with only an acknowledgment, and the DB row showed 1 files entry, 0 markdown images — and the tool output to the model no longer carried the url. Cost folding still intact (3 calls, $0.07).

What was striking

  • The bug was two genuinely different mechanisms producing the same symptom; only reading the real DB rows separated them. Theorizing alone would have conflated them.
  • The “don’t embed again” prompt instruction was never going to be reliable — the real fix is to withhold the url so re-embedding is impossible. Defaults over politeness.
  • add_message_files doing blind concatenation with no dedup is the structural root cause that makes any double-write visible.