openclaw - ✅(Solved) Fix [Bug]: Control UI: user-uploaded chat images disappear from history after send [3 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#70507Fetched 2026-04-24 05:57:15
View on GitHub
Comments
0
Participants
1
Timeline
5
Reactions
0
Participants
Timeline (top)
cross-referenced ×3labeled ×2

When a user inserts an image into Control UI chat, the image is visible immediately after sending, but disappears afterward and is no longer shown in chat history.

Root Cause

Suspected root cause

Fix Action

Fixed

PR fix notes

PR #70525: fix(gateway): preserve control UI chat images in history (Fixes #70507)

Description (problem / solution / changelog)

Summary

Problem

Control UI shows pasted or uploaded chat images immediately after send, but they disappear once the UI reloads history from chat.history.

Why it matters

The user turn was accepted and persisted, but the follow-up history view dropped the renderable image payload. That makes Control UI look like the upload never stuck.

What changed

  • Rewrote chat.history sanitization for transcript image blocks so persisted { type: "image", data, mimeType } entries come back as { type: "image", source: { type: "base64", media_type, data } }.
  • Added a gateway regression test that loads a persisted transcript message with an image block and verifies history returns the renderable Control UI shape.

What did NOT change

  • No changes to the outbound chat.send attachment flow.
  • No changes to Control UI rendering logic.
  • No changes to audio history sanitization.

Change Type

  • Bug fix

Scope

  • Gateway chat.history sanitization
  • Gateway regression coverage

Linked Issue

Closes #70507

User-visible / Behavior Changes

  • User-uploaded images in Control UI chat history stay renderable after history reload instead of disappearing.

Security Impact

  • No new capability added.
  • This keeps the already-persisted image payload renderable in Control UI history rather than replacing it with an omitted marker.

Repro + Verification

Environment

  • Local branch based on upstream/main from April 22, 2026

Steps

  1. Persist a user transcript message containing a text block plus an image block in the transcript format produced by the agent session.
  2. Request chat.history.

Expected

  • History should return the image block in the shape already rendered by Control UI.

Actual before this change

  • History stripped the top-level image data and replaced it with an omitted marker, so the Control UI renderer had nothing it could display.

Evidence

  • Added regression coverage in src/gateway/server.chat.gateway-server-chat.test.ts.
  • git diff --check passes.
  • Direct Vitest execution is blocked in this environment by the repo's existing test/non-isolated-runner.ts failure (Class extends value undefined is not a constructor or null), so I relied on focused code inspection plus the regression test addition and will let CI run the full gateway suite.
  • Standalone tsc validation in this environment also hits unrelated pre-existing module resolution / union narrowing issues outside the touched lines.

Human Verification

  • Reviewed the Control UI renderer path and matched the returned history block shape to the existing source.type === "base64" rendering branch.

Compatibility / Migration

  • No migration required.

Failure Recovery

  • Revert this PR to restore the prior history sanitization behavior.

Risks and Mitigations

  • Risk: returning image payloads in history can increase message size.
  • Mitigation: the existing chat history byte caps and final-budget enforcement remain unchanged.

Changed files

  • src/gateway/server-methods/chat.ts (modified, +59/-8)
  • src/gateway/server.chat.gateway-server-chat.test.ts (modified, +276/-1)

PR #68641: Fix Control UI image history rendering

Description (problem / solution / changelog)

Summary

  • decode transcript MediaPath and image payload variants into chat attachment content blocks
  • render image attachments in the shared chat UI so sent images stay visible after history refresh
  • add coverage for transcript media-path and image-source decoding

Testing

  • swift test --package-path apps/shared/OpenClawKit --filter 'ChatModelsTests'
  • pnpm check
  • pnpm protocol:check

Closes #68605

Changed files

  • apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMessageViews.swift (modified, +122/-9)
  • apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatModels.swift (modified, +81/-9)
  • apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift (modified, +1/-0)
  • apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatModelsTests.swift (added, +96/-0)

PR #70719: fix(gateway): persist webchat images as managed media

Description (problem / solution / changelog)

Summary

  • Problem: Control UI chat history could lose generated/assistant images because history sanitization strips raw image bytes and the UI had no durable media reference to load after reload.
  • Why it matters: image-only replies and generated images appeared live, then disappeared from the web UI chat after history reload.
  • What changed: assistant image replies are copied into managed Gateway media storage, persisted in transcript content as authenticated media URLs, served through a scoped /api/chat/media/outgoing/.../full route, and rendered by the Control UI through authenticated blob fetches.
  • What did NOT change (scope boundary): provider/model input plumbing still uses existing image attachment paths; sensitive media is still not persisted into transcript content.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #70674
  • Related #70507
  • Related #55671
  • Related #58543
  • Related #70383
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: image replies were stored/displayed as raw inline data or legacy media strings, while chat.history intentionally strips large inline image payloads for safety.
  • Missing detection / guardrail: no regression covered an assistant image reply across chat.send -> transcript persistence -> chat.history -> Control UI render.
  • Contributing context: the previous UI-side image history work handled existing renderable references, but did not create durable references for assistant/generated image payloads.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file:
    • src/gateway/server.chat.gateway-server-chat.test.ts
    • src/gateway/managed-image-attachments.test.ts
    • ui/src/ui/chat/grouped-render.test.ts
  • Scenario the test should lock in: assistant image data URL replies become managed media blocks in chat.history, raw base64 is absent from history payloads, and the UI fetches managed image URLs with auth/session headers before rendering.
  • Why this is the smallest reliable guardrail: it exercises the gateway persistence boundary, the authenticated media route, and the exact UI render path without requiring a live provider.
  • Existing test that already covers this (if any): none.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

Control UI chat reloads can display assistant/generated images through durable managed media URLs instead of depending on inline base64 history payloads.

Diagram (if applicable)

Before:
assistant image reply -> inline data / MEDIA text -> chat.history strips bytes -> no image

After:
assistant image reply -> managed media record -> transcript URL -> authenticated UI fetch -> image renders

Security Impact (required)

  • New permissions/capabilities? Yes
  • Secrets/tokens handling changed? No
  • New/changed network calls? Yes
  • Command/tool execution surface changed? No
  • Data access scope changed? Yes
  • If any Yes, explain risk + mitigation: the new media route serves only managed image records. It requires gateway HTTP auth, chat.history scope, requester session ownership for non-privileged auth, UUID attachment ids, and verifies the attachment is still referenced by the transcript before serving bytes.

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: local Node/pnpm repo checkout
  • Model/provider: mocked gateway chat dispatcher
  • Integration/channel (if any): Control UI / webchat
  • Relevant config (redacted): default local test config; QA mock-openai lane

Steps

  1. Send a webchat assistant reply payload with mediaUrls: [data:image/png;base64,...].
  2. Wait for final chat event and reload chat.history.
  3. Verify the assistant message content contains /api/chat/media/outgoing/.../full image blocks and no raw base64.
  4. Render the history message in Control UI and verify the UI fetches the managed image with auth/session headers and renders a blob preview.

Expected

  • History contains a durable image URL and renders an image after reload.

Actual

  • Before this fix, history could omit the image bytes and render no image.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios:
    • pnpm test src/gateway/managed-image-attachments.test.ts src/gateway/server.chat.gateway-server-chat.test.ts ui/src/ui/chat/grouped-render.test.ts
    • pnpm test src/gateway/server-methods/chat.directive-tags.test.ts src/gateway/managed-image-attachments.test.ts src/gateway/server.chat.gateway-server-chat.test.ts ui/src/ui/chat/grouped-render.test.ts
    • pnpm check:changed
    • pnpm build
    • pnpm openclaw qa suite --provider-mode mock-openai --scenario control-ui-qa-channel-image-roundtrip --output-dir .artifacts/qa-e2e/managed-images-control-ui-pr70719 --concurrency 1
  • Edge cases checked:
    • auth/session gating for managed media route
    • invalid/non-image sources skipped without leaking local paths
    • sensitive image media is not persisted into transcript content
    • directive tags remain persisted where needed while broadcast text remains clean
  • What you did not verify:
    • live provider image generation against a paid provider; mock QA exercised a real Control UI browser session and qa-channel image history reload.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: managed media records could outlive transcript references.
    • Mitigation: cleanup runs from chat.history, transient records expire, and the media route refuses records no longer referenced by transcript history.
  • Risk: browsers cannot attach Authorization headers to raw <img> tags.
    • Mitigation: Control UI fetches managed images with auth/session headers and renders blob URLs.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • docs/web/control-ui.md (modified, +1/-0)
  • src/gateway/managed-image-attachments.test.ts (added, +992/-0)
  • src/gateway/managed-image-attachments.ts (added, +1097/-0)
  • src/gateway/server-http.ts (modified, +23/-0)
  • src/gateway/server-methods/chat.directive-tags.test.ts (modified, +3/-4)
  • src/gateway/server-methods/chat.ts (modified, +352/-24)
  • src/gateway/server.chat.gateway-server-chat.test.ts (modified, +94/-0)
  • ui/src/ui/chat/grouped-render.test.ts (modified, +90/-0)
  • ui/src/ui/chat/grouped-render.ts (modified, +139/-18)
RAW_BUFFERClick to expand / collapse

Bug type

Regression (worked before, now fails)

Beta release blocker

No

Summary

When a user inserts an image into Control UI chat, the image is visible immediately after sending, but disappears afterward and is no longer shown in chat history.

Symptoms

  • User pastes/drops an image into chat
  • UI shows the image inline in the outgoing message
  • The message is processed successfully
  • After history refresh / subsequent render, the image is gone
  • Only the text part of the user message remains visible

Important observation

This is a different issue from assistant-media 401.

The outbound user image flow initially uses inline/base64-style message content for optimistic UI display, while persisted history appears to rely on transcript MediaPath / MediaPaths.

Reproduction

  1. Paste or upload an image into Control UI chat
  2. Send a message with text + image
  3. Observe that the image is shown immediately in the local UI
  4. Wait for normal post-send refresh / history reload
  5. Observe that the image is missing from history

What seems to happen

The client adds the image optimistically as inline image content for immediate rendering.

Later, chat history is reloaded from the server, and the persisted transcript representation is expected to include media references (MediaPath / MediaPaths).

The image disappears when that transcript rewrite/persistence step fails or misses the target user message.

Suspected root cause

The chat.send path appears to:

  • accept the user turn immediately
  • persist inbound images asynchronously
  • then rewrite the corresponding user transcript entry with MediaPath / MediaPaths

That rewrite looks brittle because it identifies the target user message by matching:

  • role === "user"
  • no existing MediaPath(s)
  • matching message text

If that rewrite misses the intended transcript entry, history will contain only the user text and no media reference.

Relevant code areas

Gateway:

  • src/gateway/server-methods/chat.ts
    • buildChatSendTranscriptMessage(...)
    • rewriteChatSendUserTurnMediaPaths(...)
    • resolveChatSendTranscriptMediaFields(...)
    • persistChatSendImages(...)

UI:

  • ui/src/ui/controllers/chat.ts
  • ui/src/ui/chat/message-normalizer.ts
  • ui/src/ui/chat/grouped-render.ts

Important note

UI rendering already appears to support transcript-based MediaPath / MediaPaths.

So if the image is missing after reload, the problem is likely not display capability itself, but that the persisted history entry never got correct media fields attached.

Expected behavior

A user-uploaded image that was accepted and processed should remain visible in chat history after reload / subsequent renders.

Actual behavior

The image is visible only temporarily right after send, then disappears from persisted history view.

OpenClaw version

v2026.4.22

Operating system

Debian

Install method

No response

Model

openai-code/gpt5.4

Provider / routing chain

openclaw

Additional provider/model setup details

No response

Logs, screenshots, and evidence

Impact and severity

No response

Additional information

No response

extent analysis

TL;DR

The issue can likely be fixed by ensuring the rewriteChatSendUserTurnMediaPaths function correctly matches and updates the target user message transcript entry with MediaPath / MediaPaths.

Guidance

  • Review the rewriteChatSendUserTurnMediaPaths function in src/gateway/server-methods/chat.ts to verify it accurately identifies the user message to update with media references.
  • Check the conditions for matching the target user message (role === "user", no existing MediaPath(s), and matching message text) to ensure they are sufficient and correctly implemented.
  • Consider adding logging or debugging statements to track the execution of rewriteChatSendUserTurnMediaPaths and verify it is correctly updating the transcript entry.
  • Examine the persistChatSendImages function to ensure images are being properly persisted and their paths correctly resolved and attached to the user message transcript.

Example

No specific code example can be provided without modifying the existing codebase, but ensuring the rewriteChatSendUserTurnMediaPaths function is correctly implemented and executed is key.

Notes

The issue seems to stem from the asynchronous persistence of images and the subsequent rewrite of the transcript entry, which may miss the target message. The display capability itself does not seem to be the problem, given that the UI can render images from transcript-based MediaPath / MediaPaths.

Recommendation

Apply a workaround or fix to the rewriteChatSendUserTurnMediaPaths function to ensure accurate matching and updating of user message transcript entries with media references, as this is the most likely cause of the issue.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

A user-uploaded image that was accepted and processed should remain visible in chat history after reload / subsequent renders.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix [Bug]: Control UI: user-uploaded chat images disappear from history after send [3 pull requests, 1 participants]