Skip to content

[Bug]: Host-path images show "Image unavailable" ("moved or deleted") when the session lacks filesystem:read, e.g. after redeeming a pre-upgrade pairing link #17906

Description

@Ultraman287

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/desktop

Steps to reproduce

  1. Run the T3 Code server (nightly) on a macOS host, reached remotely over Tailscale Serve (HTTPS).
  2. Pair the Windows desktop app (same nightly) to that host as a remote environment.
  3. In any thread (a brand-new thread reproduces it), have the assistant, or the user, send a Markdown image with a host path, e.g.:
    ![test](/Users/<user>/Library/Caches/t3-pair/display-test.png)
  4. Also send an HTTPS image as a control: ![web control](https://github.com/pingdotgg.png?size=80)

Expected behavior

The host-path image renders on Windows, like it does on the Android app connected to the same host and thread.

Actual behavior

On the Windows desktop app every host-path image shows "Image unavailable". This happens in the user's own message too, not just in assistant replies. The HTTPS control image renders fine.

Same thread, same files, viewed on the Android app (2.0, same host): all images render.

Path variants tried on Windows, all fail:

  • absolute POSIX path inside the project root (/Users/<user>/<vault>/Attachments/tmp/x.png)
  • absolute POSIX path outside the project (~/Library/Caches/...)
  • project-relative path (Attachments/tmp/x.png)
  • file:///Users/... URL
  • angle-bracket path (</Users/...png>)

Updating the Windows app from 0.0.46-nightly.20261008.2849 to 0.0.46-nightly.20261010.2935 (matching the server) did not change anything.

Before the switch to this nightly (host on 0.0.45, Windows app on the older stable build), the same Windows machine loaded /api/assets/... media URLs from this host with HTTP 200.

Impact

Minor bug or occasional failure

Version or commit

Server and Windows desktop app: 0.0.46-nightly.20261010.2935 (98beed1)

Environment

  • Host: macOS (Darwin 25.2.0, arm64), T3 Code nightly running as the background service, exposed over Tailscale Serve HTTPS
  • Client failing: Windows 11 x64, T3 Code desktop nightly, connected to the host as a remote environment
  • Client working: Android (Pixel 9), T3 Code 2.0 from the Play beta, same host
  • Providers: Claude Code harness and OpenCode 2; happens with both

Logs or stack traces

None from the client. Host server logs show no asset errors.

Screenshots, recordings, or supporting files

Screenshot (described, since I'm filing from the CLI): a fresh thread on Windows. Both the user message and the assistant reply show the "⚠ Image unavailable · fresh thread test" placeholder for the host-path image, and the GitHub avatar control image renders right below it.

Workaround

View the thread on the mobile app, or transfer the file separately.

Related: #17668 (same "Image unavailable" symptom on iPhone, for images outside the project), #14261 / #14839 (earlier Windows path issues, now closed).

Activity

  1. Ultraman287 commented on Oct 10, 2026

    @Ultraman287
    Author

    Update: found the cause. It's a permissions problem, not image rendering.

    The Windows app's session was created by redeeming a pairing link that the 0.0.45 server minted shortly before the host was upgraded to the 0.0.46 nightly. That session got the legacy scope set (orchestration:read orchestration:operate terminal:operate review:write relay:read), so it had no filesystem:read.

    On web/desktop, useAssetUrlState / useAssetUrls (apps/web/src/assets/assetUrls.ts) skip media-file resources when useFilesystemReadAccess returns false. So the image never gets an asset URL, the client never requests it, and the UI shows the generic "Image unavailable. The file may have been moved or deleted." Android rendered the same images because its session came from a link minted by the new server, so it has the full standard scopes. (Another Android session made from a pre-upgrade link also got legacy scopes and never managed to connect.)

    Fix on my side: pair again with a link minted by the new server. With filesystem:read, every host-path image renders on Windows.

    So the original report is wrong as written. What's left to suggest:

    1. When file access is the reason, show something like "This connection doesn't have file access. Pair again to view host images" instead of "may have been moved or deleted". (PermissionUpdateNotice might have covered this, but if so it was easy to miss.)
    2. Pairing links minted by a pre-granular server and redeemed after the upgrade quietly produce legacy-scope sessions. It might be worth flagging these at redemption time.

    I've retitled the issue to match. Feel free to close it if (1) and (2) are already tracked.

  2. changed the title [-][Bug]: Windows desktop shows "Image unavailable" for every host-path Markdown image from a remote macOS host (Android renders them)[/-] [+][Bug]: Host-path images show "Image unavailable" ("moved or deleted") when the session lacks filesystem:read, e.g. after redeeming a pre-upgrade pairing link[/+] on Oct 10, 2026
  3. juliusmarminge commented on Oct 10, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Thanks for the detailed report. The control image and the "no asset errors on the host" detail narrow this down a lot.

    What I checked on main (read-only):

    • The path parsing isn't the problem. classifyMarkdownImageSource (packages/client-runtime/src/markdownImages.ts) turns absolute POSIX paths, file:///Users/..., and angle-bracket paths into a media-file resource without any Windows-specific rewriting. Relative paths join the thread's workspace root. (Small aside: ~/... paths are blocked on purpose, so that variant won't render anywhere.)
    • Before asking the host for a signed URL, the desktop/web client checks whether this connection has the filesystem:read permission (useAssetUrlState in apps/web/src/assets/assetUrls.ts). If the session doesn't grant it, the client goes straight to Failure without sending assets.createUrl, and the image shows "Image unavailable". That fits what you're seeing: every host-path variant fails, HTTPS images still render, and the host logs nothing.
    • That check is new since 0.0.45. feat(auth): separate filesystem read and write permissions #9788 split filesystem read/write into separate permissions, and per packages/contracts/src/auth.ts, servers "never expand stored grants". So a remote pairing created before granular permissions may be missing filesystem:read, even though the old broad grant used to cover asset URLs. fix(auth): keep old clients connected across scope changes #10298 keeps those old clients connected and shows a permission-update notice, but it doesn't add the permission.
    • I didn't find a remote base-URL mixup. The signed URL is resolved against the remote connection's httpBaseUrl, and the desktop CSP allows https: images. The desktop's local-media fallback (fix(desktop): resolve local media linked from remote threads #10619) only runs after the host returns a not-found error, so it doesn't apply here.

    I haven't reproduced this, so the permission theory is unconfirmed. Android working on the same host would be consistent with it if that phone was paired more recently, or with a grant that includes file access.

    Could you check:

    1. On Windows, does the Files tab (or file search with @) work for this host? If it's blocked too, it's almost certainly the permission.
    2. Do you see a "permissions updated" or re-pair notice for this environment?
    3. Does removing and re-pairing the Windows app with file read access fix it?

    Related: #17668 (same symptom on iPhone, likely the same permission check), #10322, and #14261 / #14839 (Windows backslash paths, which don't apply here).

  4. added
    via-triageFiled through npx t3 triage
    bugSomething is broken or behaving incorrectly.
    on Oct 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions