openclaw - ✅(Solved) Fix BUG: media:// image URI resolves to invalid workspace media path [3 pull requests, 1 comments, 2 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#74123Fetched 2026-04-30 06:28:14
View on GitHub
Comments
1
Participants
2
Timeline
8
Reactions
0
Author
Timeline (top)
cross-referenced ×4referenced ×2commented ×1labeled ×1

Passing an inbound image as a media://... URI to the image tool fails because the URI is resolved to an invalid local path containing media:/inbound/... under the workspace. The same image succeeds when passed by its actual local file path, so this appears separate from image optimization/model handling.

Error Message

  1. Observe that OpenClaw reports a local media file not found error with a malformed path under the workspace.

Root Cause

Passing an inbound image as a media://... URI to the image tool fails because the URI is resolved to an invalid local path containing media:/inbound/... under the workspace. The same image succeeds when passed by its actual local file path, so this appears separate from image optimization/model handling.

Fix Action

Fixed

PR fix notes

PR #74186: fix(agents): detect media:// URIs before sandbox bridge path resolution

Description (problem / solution / changelog)

Summary

  • Detects media:// URIs early in resolveSandboxedBridgeMediaPath before they reach the sandbox bridge resolver
  • When an inbound fallback dir is configured, resolves media://inbound/<id> to media/inbound/<basename> via the bridge
  • When no fallback dir or stat fails, passes the raw media:// URI through so downstream loadWebMedia can resolve it via resolveMediaStoreUriToPath

Problem

Passing media://inbound/xxx.png to the image tool in a sandboxed context fails because the sandbox bridge resolver applies POSIX path.resolve(cwd, "media://inbound/xxx.png") which produces /workspace/media:/inbound/xxx.png — an invalid path. The resolveDirect() call does not throw (the path is syntactically valid), so the inbound fallback branch is never reached. The downstream loadWebMedia receives the corrupted path instead of the original media:// URI.

Changes

src/agents/sandbox-media-paths.ts

  • Added early detection of media:// URIs (regex ^media:\/\//i) before the resolveDirect() call
  • For media://inbound/<id>, extracts basename from URL pathname and tries the inbound fallback path via bridge stat/resolve
  • Falls back to returning the raw media:// URI when no fallback dir or stat fails, so loadWebMedia can resolve it normally
  • Non-media:// paths continue through the existing bridge resolution path unchanged

src/agents/sandbox-media-paths.test.ts

  • Added 4 new tests covering:
    • media://inbound URI resolved via fallback dir
    • Raw URI returned when fallback stat fails
    • Raw URI returned when no fallback dir configured
    • Non-media:// paths still go through bridge resolution

Note

This is complementary to PR #63497 which fixes media://inbound handling in the non-sandbox image tool path. This PR fixes the sandbox resolver path which is a separate bug surface.

Fixes #74123

Changed files

  • src/agents/sandbox-media-paths.test.ts (modified, +150/-1)
  • src/agents/sandbox-media-paths.ts (modified, +36/-0)

PR #74278: fix(media): reject stranded media:// URIs before workspace resolve (#74123)

Description (problem / solution / changelog)

Summary

  • Problem: Calling the image tool with a media://inbound/<file>.png URI fails with Local media file not found: <workspace>/media:/inbound/<file>.png whenever resolveMediaStoreUriToPath returns null for the URI (for example, leading whitespace from upstream prompt formatting bypasses the strict scheme check at web-media.ts:61 while the lenient upstream check at web-media.ts:374 lets the URI continue). The unresolved URI then hits path.resolve(workspaceDir, mediaUrl) and the media:// scheme is collapsed into a media:/ directory component.
  • Why it matters: End-to-end image understanding fails for the canonical media-store URI form even when the file exists on disk. Documented at #74123.
  • What changed: loadWebMediaInternal now throws LocalMediaAccessError("not-found", ...) immediately when a media:// URI survives the store lookup, surfacing the original URI verbatim in the error. The workspace-resolve branch additionally short-circuits for any value that classifyMediaReferenceSource(...).hasScheme flags, so future scheme-bearing inputs (data:, file:, http(s):) cannot be mis-joined with workspaceDir either.
  • What did NOT change (scope boundary): Zero changes to media-reference.ts (the URI parser was recently refactored and a deeper change risked overlap with maintainer-owned work), zero changes to store.ts/fetch.ts/local-media-access.ts, no new public exports, no schema/contract/error-code additions. Existing media://inbound/<id> happy-path resolution and the existing path-not-allowed / invalid-path error codes are untouched.

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 (image tool error surface)
  • CI/CD / infra

Linked Issue/PR

  • Closes #74123
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: loadWebMediaInternal had two scheme-handling layers that disagreed about leniency. Line 374 strips a MEDIA: prefix unless the input matches /^\s*media:\/\//i (lenient — accepts leading whitespace). Line 61 inside resolveMediaStoreUriToPath returns null unless the input matches /^media:\/\//i (strict — no whitespace). Inputs that fall in the gap (whitespace-prefixed media://...) skip the strip, return null from the store resolver, and stay as " media://inbound/<id>" until they hit path.resolve(workspaceDir, mediaUrl), which produces the corrupted path the issue reports.
  • Missing detection / guardrail: The path.resolve site at line 490 only excluded path.isAbsolute and Windows-drive shapes, never URI schemes.
  • Contributing context: The two-layer strip-then-resolve pattern was introduced to be lenient with LLM-formatted media tags (the tooling sometimes emits MEDIA : /tmp/x.png with whitespace). The leniency intentionally covers the MEDIA: prefix case; the media:// URI case was not similarly protected on the resolver side.

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/media/web-media.test.ts (describe("loadWebMedia") block).
  • Scenario the test should lock in: three new cases —
    • rejects stranded media:// URIs without joining them under workspaceDirloadWebMedia(" media://inbound/<id>", { workspaceDir }) rejects with code: "not-found" and a message containing media://, never <workspaceDir>${path.sep}media:.
    • does not workspace-resolve media store URIs whose hostname is unsupportedmedia://outbound/tiny.png with a workspaceDir still rejects with code: "path-not-allowed" and never produces a workspace-prefixed path.
    • does not workspace-resolve data: URIs that reach the local-path branchdata:application/octet-stream;base64,AA== with a workspaceDir rejects without producing a <workspaceDir>${path.sep}data: path (defense-in-depth for the symmetric scheme leak).
  • Why this is the smallest reliable guardrail: All scheme-bearing inputs converge through loadWebMediaInternal. Asserting "no URI is ever workspace-resolved" at this seam covers every caller (image tool, TTS, channel media uploads).
  • Existing test that already covers this: N/A — pre-existing tests at web-media.test.ts:435,454,474,480,486 all exercised the happy path or upstream-resolver-throws path; none exercised the stranded-URI fall-through.
  • If no new test is added, why not: 3 new test cases added.

User-visible / Behavior Changes

A media:// URI that did not resolve to a physical media file now produces Local media file not found: media://... instead of Local media file not found: <workspace>/media:/.... No other behavior changes; happy-path resolution and existing error codes are unaffected.

Diagram (if applicable)

Before:
loadWebMedia("media://inbound/foo.png", { workspaceDir: "/ws" })
  -> resolveMediaStoreUriToPath returns null (e.g. whitespace-prefixed input)
  -> mediaUrl unchanged
  -> path.resolve("/ws", "media://inbound/foo.png")
       ↳ "/ws/media:/inbound/foo.png"   ← scheme collapsed into directory
  -> readLocalFileSafely throws ENOENT
  -> "Local media file not found: /ws/media:/inbound/foo.png"

After:
loadWebMedia("media://inbound/foo.png", { workspaceDir: "/ws" })
  -> resolveMediaStoreUriToPath returns null
  -> guard fires: throw LocalMediaAccessError("not-found",
                       `Local media file not found: media://inbound/foo.png`)
  -> Workspace path is never consulted; original URI surfaces in the error.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No — the change tightens path resolution by rejecting URIs earlier, so the fix can only make path access narrower (never wider).

Repro + Verification

Environment

  • OS: Windows 11 host running tests against the openclaw monorepo
  • Runtime/container: Node 24.11.0, pnpm 10.20.0
  • Model/provider: N/A (the bug is in pre-fetch path resolution)
  • Integration/channel (if any): image understanding (any channel that produces media:// URIs)
  • Relevant config (redacted): agents.defaults.imageModel.* configured to any provider; the bug is independent of the model provider.

Steps

  1. pnpm install
  2. Reproduce on main (pre-fix) by adding the new tests against unmodified source: pnpm test src/media/web-media.test.ts -- --testNamePattern="stranded" → fails because a stranded media:// URI gets workspace-joined.
  3. Apply this branch: pnpm test src/media/web-media.test.ts → all 36 tests pass (33 baseline + 3 new).
  4. Targeted gates:
    • pnpm tsgo:core → clean
    • pnpm tsgo:core:test → clean
    • pnpm format src/media/web-media.ts src/media/web-media.test.ts → no changes
    • pnpm check:changed → exit 0

Expected

  • Stranded media:// URI → LocalMediaAccessError({ code: "not-found", message: "Local media file not found: media://..." }).
  • The workspace path never appears anywhere in the error chain.
  • media://outbound/... continues to error as path-not-allowed.
  • data: URIs do not get workspace-joined either.

Actual

  • Matches expected on every new test.

Evidence

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

Pre-fix run on the new test (against unmodified source): rejects with the corrupted <workspace>${path.sep}media: path, failing the .not.toMatchObject assertion. Post-fix on this branch: 36/36 pass.

Human Verification (required)

  • Verified scenarios:
    • Pre-fix: ran the new tests against unmodified web-media.ts and saw the corrupted path appear in the error message.
    • Post-fix: full pnpm test src/media/web-media.test.ts (36 tests) passes locally.
    • Type-checks pass for both tsgo:core and tsgo:core:test.
    • pnpm check:changed is green.
  • Edge cases checked:
    • Whitespace-prefixed media:// URI is the deterministic regression scenario (lenient strip, strict resolve).
    • media://outbound/... continues to throw path-not-allowed, behavior unchanged.
    • data: URI never receives a workspace prefix.
    • https:// URIs are routed to fetchRemoteMedia before reaching the workspace branch (existing behavior, unaffected).
    • Windows drive paths still satisfy WINDOWS_DRIVE_RE first; the new !hasScheme test fires only after that exclusion, and classifyMediaReferenceSource correctly distinguishes drives via looksLikeWindowsDrivePath so they are not blocked.
  • What I did not verify: A live channel-driven reproduction of the user's exact upstream surface (no Telegram / WhatsApp test rig configured locally). The fix is asserted at the boundary that drives the corrupted path (loadWebMedia), which every channel and the image tool funnel through.

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 — happy-path media:// resolution is unchanged; the only user-visible change is a clearer error message when resolution fails.
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: A caller intentionally passed a media:// URI to be path-resolved as a literal directory under workspaceDir.
    • Mitigation: No such caller was found in the repo. Treating a URI as a literal subdirectory would have produced the same media:/... corruption that the bug report flags as broken; the new behavior is a strict improvement.
  • Risk: The chosen leniency mismatch between line 374 and resolveMediaStoreUriToPath is the actual root cause and a future PR could fix that resolver instead.
    • Mitigation: The early-throw is harmless if the resolver becomes more lenient — the throw simply never fires because mediaUrl will already be an absolute physical path. The fix is forward-compatible with a deeper refactor.

CI note

The repo's Parity gate check (thread-memory-isolation 8/12) is currently red across other unrelated PRs on main; this is a known main regression and not caused by this change. oxlint shows 3 pre-existing errors in extensions/codex/index.test.ts:75 on clean main that are also unrelated to this diff.

🤖 AI-assisted (Claude Code). Test level: fully tested via pnpm test src/media/web-media.test.ts. I understand the change.

Changed files

  • src/media/web-media.test.ts (modified, +48/-0)
  • src/media/web-media.ts (modified, +19/-2)

PR #63497: fix(agents): support media://inbound URIs in image tool #63476

Description (problem / solution / changelog)

Summary

  • Problem: With a text-only primary model (e.g. zai/glm-5-turbo), inbound images are not passed as multimodal input; agents rely on the image tool. Gateway claim-check references use media://inbound/<id>, but the tool either rejected them as unsupported schemes or corrupted them when normalizing MEDIA:-style prefixes (stripping media: from media:// produced //inbound/... and broke local resolution).
  • Why it matters: Users hitting Telegram (and similar) flows could see vision failures or “no image” behavior even though files exist under the media store.
  • What changed: Resolve media://inbound/<id> via resolveMediaBufferPath + loadWebMedia with localRoots: [getMediaDir()], aligned with native prompt image loading. Only strip the MEDIA: tag when the value is not already a media:// gateway URI. Added Vitest coverage with OPENCLAW_STATE_DIR.
  • What did NOT change: Sandbox/workspace rules for ordinary paths and URLs; no changes to gateway offload or channel parsers.

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

  • Fixes #63476

User-visible / Behavior Changes

The image tool now accepts gateway claim-check URIs media://inbound/<id> (and MEDIA:media://inbound/<id>), resolving them to on-disk inbound media the same way native prompt image loading does.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No (same tool; additional allowed URI form, still constrained by resolveMediaBufferPath and media dir roots)
  • Data access scope changed? No (only resolves IDs already under the configured inbound media store)

Repro + Verification

Environment

  • OS: Linux (CI-style); issue originally macOS + Telegram + ZAI
  • Runtime: Node 22+ / Vitest
  • Model/provider: N/A for automated test (stubbed media-understanding + fetch)
  • Integration/channel: N/A in unit tests
  • Relevant config (redacted): OPENCLAW_STATE_DIR pointing at a temp dir containing media/inbound/<file>

Steps

  1. Set OPENCLAW_STATE_DIR to a temp directory; create media/inbound/claim-check-test.png with a tiny PNG payload.
  2. Run pnpm test -- src/agents/tools/image-tool.test.ts -t "gateway media".
  3. (Optional) Full file: pnpm test -- src/agents/tools/image-tool.test.ts.

Expected

  • Tool executes successfully and returns text result; fetch stub invoked once for the generic image path.

Actual

  • Both tests pass locally; pnpm build passes for the tree that includes this change.

Evidence

  • Failing test/log before + passing after (behavior covered by new tests; prior behavior was wrong path / unsupported URI)
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: Vitest image tool gateway media://inbound URIs; full src/agents/tools/image-tool.test.ts; pnpm build after the change.
  • Edge cases checked: bare media://inbound/... vs MEDIA:media://inbound/....
  • What I did not verify: Live Telegram + ZAI end-to-end (requires external keys and channel).

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

Failure Recovery (if this breaks)

  • Revert the commit touching src/agents/tools/image-tool.ts (and tests/changelog if needed).
  • Symptoms: media://inbound/... might again be rejected or mis-resolved.

Risks and Mitigations

  • Risk: Broader interpretation of media:// inputs.
    • Mitigation: Only media://inbound/<id> is handled; ID validation matches existing resolveMediaBufferPath guards.

Changed files

  • CHANGELOG.md (modified, +2/-0)
  • src/agents/tools/image-tool.test.ts (modified, +66/-0)
  • src/agents/tools/image-tool.ts (modified, +60/-11)

Code Example

Observed failure using a media:// URI:

[tools] image failed: Local media file not found: .../workspace/media:/inbound/<image-file>.png

The malformed part is:

Control check on the same image using the actual local file path succeeded and produced normal image transcription/description output. This indicates the image file itself is valid and the image processing path can work; the failure is specific to media:// URI resolution.
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

Passing an inbound image as a media://... URI to the image tool fails because the URI is resolved to an invalid local path containing media:/inbound/... under the workspace. The same image succeeds when passed by its actual local file path, so this appears separate from image optimization/model handling.

Steps to reproduce

  1. Receive or otherwise have an inbound image stored in OpenClaw media storage.
  2. Call image understanding with a media://inbound/<image-file>.png reference.
  3. Observe that OpenClaw reports a local media file not found error with a malformed path under the workspace.
  4. Call image understanding on the same image using its actual local file path.
  5. Observe that image understanding succeeds.

Expected behavior

media://inbound/<image-file>.png should resolve to the corresponding OpenClaw-managed media file and be processed the same way as the actual local file path.

Actual behavior

The media://... URI resolves to an invalid path shaped like:

.../workspace/media:/inbound/<image-file>.png

The image tool then fails before image processing with:

Local media file not found: .../workspace/media:/inbound/<image-file>.png

OpenClaw version

2026.4.26 (be8c246)

Operating system

Ubuntu 26.04 LTS

Install method

npm global package install

Model

Image-capable model configured through OpenClaw image tooling; the failure occurs before model analysis.

Provider / routing chain

OpenClaw image tool -> media URI/path resolution -> configured image model

Additional provider/model setup details

No response

Logs, screenshots, and evidence

Observed failure using a media:// URI:

[tools] image failed: Local media file not found: .../workspace/media:/inbound/<image-file>.png

The malformed part is:

Control check on the same image using the actual local file path succeeded and produced normal image transcription/description output. This indicates the image file itself is valid and the image processing path can work; the failure is specific to media:// URI resolution.

Impact and severity

Image handling works only when the caller has or derives the actual local filesystem path. Any workflow that relies on media://... references for inbound media can fail before image processing.

Additional information

This is separate from the sharp/image optimization dependency issue reported in #74111. After image optimization was repaired and absolute local image paths succeeded, the media:// URI form continued to fail with the malformed workspace/media:/inbound/... path.

extent analysis

TL;DR

The issue can be resolved by correctly handling the media:// URI resolution to point to the actual OpenClaw-managed media file location.

Guidance

  • Verify that the media:// URI resolution is correctly configured to point to the OpenClaw-managed media storage location, rather than the workspace directory.
  • Check the OpenClaw documentation for any specific settings or environment variables that need to be set for correct media:// URI resolution.
  • Consider modifying the code to handle media:// URIs separately from local file paths, to ensure correct resolution and processing.
  • Test the image tool with different media:// URI formats to identify any specific patterns or edge cases that may be causing the issue.

Example

No code snippet is provided as the issue is related to configuration and URI resolution rather than code syntax.

Notes

The issue is specific to the media:// URI resolution and does not appear to be related to image optimization or model handling. The fact that the image tool works with actual local file paths suggests that the issue is isolated to the URI resolution mechanism.

Recommendation

Apply a workaround to correctly handle media:// URI resolution, such as modifying the code to use a custom resolution mechanism or setting environment variables to point to the correct media storage location. This is recommended because the issue is specific to the media:// URI resolution and a workaround can be implemented without affecting other parts of the system.

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

media://inbound/<image-file>.png should resolve to the corresponding OpenClaw-managed media file and be processed the same way as the actual local file path.

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: media:// image URI resolves to invalid workspace media path [3 pull requests, 1 comments, 2 participants]