You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I have searched Warp bugs and there are no duplicates. Closest match is Displayed image is stale when underlying file is changed at the same path #12802 ("Displayed image is stale when underlying file is changed at the same path"), which was fixed by PR Invalidate cached local-file images in AI blocks when the file changes #12840 — but that fix is explicitly scoped to AI-block image display only. The PR description states: "non-blocklist callers (themes, notebooks) keep path-only caching (content_version: None), so their behavior is unchanged." The standalone Markdown document viewer (FileNotebookView, opening a .md file from the File Tree / Code Editor) is one of those un-fixed "notebooks" callers, and reproduces the identical bug today on current master (13902088). Also related: Image cache is not getting cleared #9789 ("Image cache is not getting cleared", unbounded growth — different symptom, same two caches).
I have searched Warp known issues page and my issue is not there.
Describe the bug
When a Markdown document is opened in the rendered Markdown viewer and it embeds a local image via , regenerating that image file on disk (even via an atomic temp-file-plus-rename replace, i.e. a new inode) does not update the rendered image. I tried every refresh mechanism available in the UI and all four failed:
Closing the document pane and reopening the file — stale image persists.
Toggling between Rendered and Raw view modes — stale image persists.
The pane header overflow menu's "Refresh file" action — refreshes the document's text content but not the embedded images.
Re-editing/re-saving the Markdown file itself (forces a text reload) — embedded images still stale.
Only writing the image to a brand-new file path (and updating the Markdown reference) shows the new content, which points at a cache keyed purely by path.
Root cause (from reading the open source)
Confirmed by reading the checked-out source at 13902088 (current master), not just inferred from symptoms.
The cache key never changes when a Markdown-embedded image's file content changes:
resolve_asset_source_relative_to_directory() in crates/editor/src/content/edit.rs (lines ~78–100) builds the AssetSource::LocalFile { path, content_version: None } for every image referenced from Markdown — both the absolute-path branch (line 87) and the relative-path branch (line 98) hardcode content_version: None.
AssetSource::LocalFile has an optional content_version: Option<LocalFileContentVersion> field (crates/warpui_core/src/assets/asset_cache.rs:125-135) that fingerprints mtime + file size specifically to solve this class of bug (doc comment: "so a file whose contents change on disk is treated as a distinct asset and re-read, rather than served from a now-stale cache entry"). The helper that populates it, AssetSource::with_local_file_content_version() (asset_cache.rs:144-156), is called from exactly one call site in the whole codebase: app/src/ai/blocklist/block.rs:1766, inside AI-block image resolution. The Markdown editor's resolve_asset_source_relative_to_directory() never calls it.
Because the cache key (AssetHandle{ source, asset_type }) is identical before and after the file changes, AssetCache::load_asset() (asset_cache.rs:410-469) hits assets.contains_key(&key) → true on line 419 and returns the already-loaded (stale) entry without ever re-reading the file.
A second cache layer compounds this: ImageCache::image() (crates/warpui_core/src/image_cache.rs:862-966) additionally caches the rendered (resized/rasterized) image, keyed by a DefaultHasher hash of the same AssetSource plus size/fit options (line 873-875) — so even if the first layer were fixed, this layer needs the same content-version-aware key to avoid serving a stale rasterization. There is an evict_size() method (line 836) intended to support targeted eviction, but it is marked #[allow(dead_code)] with the comment "Called by the debounce eviction pass added in the main changeset" / `TODO(APP-3877): remove #[allow(dead_code)] once the debounce eviction pass wires this up" — i.e. that eviction path isn't wired to any caller yet.
Why all four refresh attempts fail, specifically:
Close/reopen and Rendered/Raw toggle: both re-render from the same AssetSource derived from the same path, so they hit the same cache key.
"Refresh file" (FileNotebookAction::ReloadFile, handled by reload_file() in app/src/notebooks/file/mod.rs:570-583) calls self.open(path, session, ctx), which re-reads the containing Markdown document's own bytes from disk — but the embedded image asset sources are resolved independently via resolve_asset_source_relative_to_directory(), whose output is cache-key-identical to before. The button's own text/reload path works correctly; only the nested image assets are left stale, which is why it looks like a targeted bug in the button rather than a general cache problem.
All four surfaces trace back to the same single defect: the Markdown image resolution path never opts into content_version. I'm filing this as one ticket (with "Refresh file" listed as an affected surface) rather than two, since fixing the resolver is a single, shared fix — no separate plumbing exists for the button.
Proposed fix
Mirror what PR #12840 already did for the AI-block path, applied to the Markdown editor's image resolution instead:
In crates/editor/src/content/edit.rs, have resolve_asset_source_relative_to_directory() (or its caller, off the render hot path per the existing doc comment on with_local_file_content_version()) call .with_local_file_content_version() before constructing the final AssetSource::LocalFile, so content_version is populated from mtime + file size instead of hardcoded to None.
This should be enough for both cache layers, since content_version is part of AssetSource, which is the key for both AssetCache (decoded bytes) and ImageCache (rendered pixels).
Given the existing PR's note that this metadata read is "off the render hot path," care should be taken about where in the Markdown parse/render pipeline the stat call happens so it isn't done per-frame — the AI-block precedent (stat once when the block resolves its image sources: creation / output completion / restore / shell-change) is a reasonable model for "stat once when the document's block list is (re)built, including on explicit Refresh file."
Separately (lower priority, already tracked in Image cache is not getting cleared #9789/APP-3877): ImageCache::evict_size() is unused dead code; wiring it into an actual eviction/debounce pass would also help the unbounded-growth issue, though it's not required to fix the staleness reported here — a changed content_version alone makes stale entries unreachable (they'll just accumulate until the existing size-based AssetCache eviction or window-resize/size-change eviction reclaims them).
I'd frame the specifics of where to trigger the stat as a judgment call for whoever picks this up — I don't have full visibility into how block-list resolution timing compares to Markdown's parse/layout pipeline, so the AI-block PR is offered as prior art, not a prescription.
Related: #13652 (raw-HTML <img> support — same markdown viewer surface; spec PR: #13656) and #13657 (rendered SVG/image viewer panel — filed the same day, would reuse this same image pipeline).
To reproduce
Create a Markdown file that embeds a local image, e.g. , and open it in Warp's Markdown viewer (rendered mode).
Confirm the image renders.
Overwrite chart.svg on disk with different visual content, using an atomic temp-file + rename replace (so the file gets a new inode, ruling out any naive "same inode" caching theory).
Try, in turn: closing and reopening the document pane; toggling Rendered ↔ Raw; the pane header overflow menu's "Refresh file" action.
Actual: in all three cases, the original image is still displayed.
Expected: the updated image content is displayed after any of these refresh actions — most of all "Refresh file," an explicit user-triggered refresh affordance that currently silently no-ops for images while appearing to work (it does refresh the surrounding text).
Expected behavior
An explicit refresh of a Markdown document (via "Refresh file," reopening the pane, or toggling view mode) should reflect the current on-disk content of every embedded image, not just the document's own text.
Screenshots, videos, and logs
N/A — this is a source-level analysis; happy to attach a screen recording if useful for triage.
Operating system (OS)
macOS
Operating system and version
macOS 27.0 (Darwin 27.0.0)
Shell Version
zsh
Current Warp version
0.2026.07.01.09.21.01
Regression
No, this bug or issue has existed throughout my experience using Warp
Recent working Warp date
N/A
Additional context
Source read at commit 13902088 (current master as of 2026-07-13; the local checkout's active branch, fbartho/gh13652-markdown-img-sizing-spec, is that same commit plus only additive files under specs/GH13652/, so all cited source files are identical to master).
Does this block you from using Warp daily?
No
Is this an issue only in Warp?
Yes, I confirmed that this only happens in Warp, not other terminals.
Pre-submit Checks
content_version: None), so their behavior is unchanged." The standalone Markdown document viewer (FileNotebookView, opening a.mdfile from the File Tree / Code Editor) is one of those un-fixed "notebooks" callers, and reproduces the identical bug today on currentmaster(13902088). Also related: Image cache is not getting cleared #9789 ("Image cache is not getting cleared", unbounded growth — different symptom, same two caches).Describe the bug
When a Markdown document is opened in the rendered Markdown viewer and it embeds a local image via
, regenerating that image file on disk (even via an atomic temp-file-plus-rename replace, i.e. a new inode) does not update the rendered image. I tried every refresh mechanism available in the UI and all four failed:Only writing the image to a brand-new file path (and updating the Markdown reference) shows the new content, which points at a cache keyed purely by path.
Root cause (from reading the open source)
Confirmed by reading the checked-out source at
13902088(currentmaster), not just inferred from symptoms.The cache key never changes when a Markdown-embedded image's file content changes:
resolve_asset_source_relative_to_directory()incrates/editor/src/content/edit.rs(lines ~78–100) builds theAssetSource::LocalFile { path, content_version: None }for every image referenced from Markdown — both the absolute-path branch (line 87) and the relative-path branch (line 98) hardcodecontent_version: None.AssetSource::LocalFilehas an optionalcontent_version: Option<LocalFileContentVersion>field (crates/warpui_core/src/assets/asset_cache.rs:125-135) that fingerprints mtime + file size specifically to solve this class of bug (doc comment: "so a file whose contents change on disk is treated as a distinct asset and re-read, rather than served from a now-stale cache entry"). The helper that populates it,AssetSource::with_local_file_content_version()(asset_cache.rs:144-156), is called from exactly one call site in the whole codebase:app/src/ai/blocklist/block.rs:1766, inside AI-block image resolution. The Markdown editor'sresolve_asset_source_relative_to_directory()never calls it.AssetHandle{ source, asset_type }) is identical before and after the file changes,AssetCache::load_asset()(asset_cache.rs:410-469) hitsassets.contains_key(&key)→trueon line 419 and returns the already-loaded (stale) entry without ever re-reading the file.ImageCache::image()(crates/warpui_core/src/image_cache.rs:862-966) additionally caches the rendered (resized/rasterized) image, keyed by aDefaultHasherhash of the sameAssetSourceplus size/fit options (line 873-875) — so even if the first layer were fixed, this layer needs the same content-version-aware key to avoid serving a stale rasterization. There is anevict_size()method (line 836) intended to support targeted eviction, but it is marked#[allow(dead_code)]with the comment "Called by the debounce eviction pass added in the main changeset" / `TODO(APP-3877): remove #[allow(dead_code)] once the debounce eviction pass wires this up" — i.e. that eviction path isn't wired to any caller yet.Why all four refresh attempts fail, specifically:
AssetSourcederived from the same path, so they hit the same cache key.FileNotebookAction::ReloadFile, handled byreload_file()inapp/src/notebooks/file/mod.rs:570-583) callsself.open(path, session, ctx), which re-reads the containing Markdown document's own bytes from disk — but the embedded image asset sources are resolved independently viaresolve_asset_source_relative_to_directory(), whose output is cache-key-identical to before. The button's own text/reload path works correctly; only the nested image assets are left stale, which is why it looks like a targeted bug in the button rather than a general cache problem.All four surfaces trace back to the same single defect: the Markdown image resolution path never opts into
content_version. I'm filing this as one ticket (with "Refresh file" listed as an affected surface) rather than two, since fixing the resolver is a single, shared fix — no separate plumbing exists for the button.Proposed fix
Mirror what PR #12840 already did for the AI-block path, applied to the Markdown editor's image resolution instead:
crates/editor/src/content/edit.rs, haveresolve_asset_source_relative_to_directory()(or its caller, off the render hot path per the existing doc comment onwith_local_file_content_version()) call.with_local_file_content_version()before constructing the finalAssetSource::LocalFile, socontent_versionis populated from mtime + file size instead of hardcoded toNone.content_versionis part ofAssetSource, which is the key for bothAssetCache(decoded bytes) andImageCache(rendered pixels).ImageCache::evict_size()is unused dead code; wiring it into an actual eviction/debounce pass would also help the unbounded-growth issue, though it's not required to fix the staleness reported here — a changedcontent_versionalone makes stale entries unreachable (they'll just accumulate until the existing size-basedAssetCacheeviction or window-resize/size-change eviction reclaims them).I'd frame the specifics of where to trigger the stat as a judgment call for whoever picks this up — I don't have full visibility into how block-list resolution timing compares to Markdown's parse/layout pipeline, so the AI-block PR is offered as prior art, not a prescription.
Related: #13652 (raw-HTML
<img>support — same markdown viewer surface; spec PR: #13656) and #13657 (rendered SVG/image viewer panel — filed the same day, would reuse this same image pipeline).To reproduce
, and open it in Warp's Markdown viewer (rendered mode).chart.svgon disk with different visual content, using an atomic temp-file + rename replace (so the file gets a new inode, ruling out any naive "same inode" caching theory).Expected behavior
An explicit refresh of a Markdown document (via "Refresh file," reopening the pane, or toggling view mode) should reflect the current on-disk content of every embedded image, not just the document's own text.
Screenshots, videos, and logs
N/A — this is a source-level analysis; happy to attach a screen recording if useful for triage.
Operating system (OS)
macOS
Operating system and version
macOS 27.0 (Darwin 27.0.0)
Shell Version
zsh
Current Warp version
0.2026.07.01.09.21.01
Regression
No, this bug or issue has existed throughout my experience using Warp
Recent working Warp date
N/A
Additional context
Source read at commit
13902088(currentmasteras of 2026-07-13; the local checkout's active branch,fbartho/gh13652-markdown-img-sizing-spec, is that same commit plus only additive files underspecs/GH13652/, so all cited source files are identical tomaster).Does this block you from using Warp daily?
No
Is this an issue only in Warp?
Yes, I confirmed that this only happens in Warp, not other terminals.