Skip to content

bug: image previews never render on Windows — frontend sends backslash paths to /api/workspace/files/serve #246

Description

@gucazcode

Version: 0.9.21 · OS: Windows 11 · Browser: Chrome

Summary

On Windows, no image ever renders inline. display_file draws the viewer panel — title bar, zoom
controls, 100% — but the image itself is a broken-image icon, and the size readout shows 0 B.

The backend is fine. The frontend builds the URL from a raw Windows path, without converting
backslashes to forward slashes and without URL-encoding
, while the serve endpoint expects
forward slashes — as its own comment says.

Reproduce

  1. On Windows, open a workspace and put a PNG in it.
  2. Ask the agent to display_file that PNG (or open it from the file browser).
  3. The viewer frame appears; the image is broken; size reads 0 B.

Root cause

Frontend_app/immutable/nodes/2.*.js:

`/api/workspace/files/serve/${n.filePath.startsWith('/') ? n.filePath.slice(1) : n.filePath}`

n.filePath on Windows is e.g. C:\Users\user\workspace\example.png (confirmed — it appears
verbatim as the broken image's alt text). It is interpolated raw: no separator normalisation,
no encoding.

Backendrouters/workspace.py:

@router.get("/serve/{file_path:path}")
async def serve_static(file_path: str):
    # Windows paths arrive as "C:/Users/..." - don't prepend /
    if len(file_path) >= 2 and file_path[1] == ":":
        target = Path(file_path).resolve()
    else:
        target = Path("/" + file_path).resolve()

The comment states the contract — C:/Users/..., forward slashes — which the frontend does
not meet.

Evidence the backend is correct

This URL, in a logged-in browser, renders the image correctly:

http://127.0.0.1:8000/api/workspace/files/serve/C:/Users/user/workspace/example.png

Same file, same session, forward slashes instead of backslashes. So routing, auth, MIME detection
and streaming all work; only the URL the frontend builds is wrong.

Suggested fix

Normalise separators before interpolating, and encode each segment:

const p = n.filePath.replace(/\\/g, '/');
const url = `/api/workspace/files/serve/${p.split('/').map(encodeURIComponent).join('/')}`;

Encoding also fixes paths containing spaces, # or ?, which will fail on every platform.

Related: the bundle already has a resolver, de(), which prefers full_path, falls back to
path, and joins relative paths to the workspace root. The image viewer bypasses it — and de()
does not normalise separators either, so both call sites likely need the same treatment.

Workaround in use

Until this is fixed upstream, we patch the built bundle in place, rewriting the interpolation to
normalise separators. It works, but it is a stopgap: an upgrade replaces the bundle and silently
reverts it
, so the patch has to be re-applied and verified after every update. A fix in the
source is the real answer.

Anyone hitting this before a release can also open the file directly at
/api/workspace/files/view?path=<url-encoded absolute path>, which serves correctly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions