openclaw - ✅(Solved) Fix diffs: viewer URL returns 'Diff not found' despite artifact written to disk (v2026.4.1) [1 pull requests, 3 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#59227Fetched 2026-04-08 02:27:13
View on GitHub
Comments
3
Participants
2
Timeline
8
Reactions
0
Timeline (top)
commented ×3closed ×1cross-referenced ×1locked ×1

The diffs tool generates viewer URLs that immediately return Diff not found even when called from the host machine via curl. The artifact files are written to disk correctly.

Root Cause

Root Cause Hypothesis

Fix Action

Workaround

PNG delivery works correctly. Use mode=file or mode=both and send the filePath via the message tool instead of sharing the viewer URL.

PR fix notes

PR #59341: diffs: add configurable viewer base URL

Description (problem / solution / changelog)

Summary

  • Problem: diffs viewer URLs defaulted to raw loopback, which breaks in same-host proxy deployments that intentionally treat 127.0.0.1 as a trusted proxy and fail closed without forwarded client-IP headers.
  • Why it matters: the earlier localhost fast-path idea fixed one topology by weakening the trusted-proxy invariant, which is the wrong tradeoff.
  • What changed:
    • added plugin-owned viewerBaseUrl config for persistent viewer-link origin/path control
    • kept per-call baseUrl as the highest-precedence override
    • preserved the existing fail-closed viewer access policy
    • added config/tool coverage for normalization and precedence
    • documented the supported Oracle/Tailscale proxy path and added a changelog entry
  • What did NOT change:
    • no access-policy widening
    • no remoteAddress heuristic bypass
    • no storage or token model changes

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 #59227
  • Related #58598
  • This PR fixes a bug or regression

Root Cause / Regression History (if applicable)

  • Root cause: viewer URL generation assumed raw loopback was the right client origin, while the diffs HTTP gate correctly treated loopback-in-trustedProxies as ambiguous proxy traffic that must fail closed without forwarded headers.
  • Missing detection / guardrail: there was no explicit viewer-origin seam for this plugin, so callers had to pass baseUrl on every tool call or accept the raw loopback fallback.
  • Prior context (git blame, prior PR, issue, or refactor if known): fix(diffs): harden viewer proxy access (30a1690323) intentionally preserved the fail-closed guard.
  • Why this regressed now: the issue surfaced once raw loopback viewer URLs met a same-host trusted-proxy topology.
  • If unknown, what was ruled out: ruled out artifact-registry and disk-persistence issues; requests were being denied before artifact lookup.

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:
    • extensions/diffs/src/config.test.ts
    • extensions/diffs/src/tool.test.ts
    • extensions/diffs/src/store.test.ts
  • Scenario the tests now lock in:
    • plugin viewerBaseUrl is normalized and used when no per-call baseUrl is present
    • per-call baseUrl still overrides plugin config
    • trusted-proxy loopback requests without valid provenance headers remain denied by default
  • Why this is the smallest reliable guardrail: it fixes the real seam (viewer URL generation) while separately preserving the existing access gate behavior.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

Diffs can now be configured with plugins.entries.diffs.config.viewerBaseUrl, so viewer links can point at a stable proxy/public origin without passing baseUrl on every tool call. Access policy is unchanged: loopback trusted-proxy requests still fail closed unless remote viewers are explicitly enabled.

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS host
  • Runtime/container: local repo runtime
  • Model/provider: N/A
  • Integration/channel (if any): diffs plugin HTTP viewer route
  • Relevant config (redacted): gateway.trustedProxies=["127.0.0.1"]

Steps

  1. Configure diffs with a persistent viewer origin or pass baseUrl per call.
  2. Generate diff viewer URLs in view / both mode.
  3. Re-check loopback trusted-proxy denial behavior.

Expected

  • Viewer URLs can target a stable proxy/public origin without weakening the viewer access gate.
  • Trusted-proxy loopback traffic without client-origin headers stays denied by default.

Actual

  • This PR adds the explicit viewer-origin seam and preserves the existing fail-closed HTTP policy.

Evidence

Attach at least one:

  • Passing targeted test coverage
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios:
    • pnpm test -- extensions/diffs/src/config.test.ts extensions/diffs/src/tool.test.ts extensions/diffs/src/store.test.ts
    • pnpm build
    • pnpm check
    • pnpm format
  • Edge cases checked:
    • normalized plugin viewerBaseUrl
    • per-call baseUrl precedence over plugin config
    • trusted-proxy loopback denial path with and without forwarded headers when remote viewers stay disabled
  • What you did not verify: full pnpm test suite (reviewer explicitly requested targeted-only verification for this prep pass).

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/No) Yes
  • Config/env changes? (Yes/No) Optional new plugin config
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps:

Risks and Mitigations

  • Risk: deployments may still point viewers at unusable origins if neither viewerBaseUrl nor per-call baseUrl is set.
    • Mitigation: keep the existing raw-loopback fallback, document the proxy topology caveat, and let explicit config override it safely.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • docs/install/oracle.md (modified, +2/-0)
  • docs/platforms/oracle.md (modified, +2/-0)
  • docs/tools/diffs.md (modified, +32/-3)
  • extensions/diffs/README.md (modified, +22/-1)
  • extensions/diffs/index.ts (modified, +8/-3)
  • extensions/diffs/openclaw.plugin.json (modified, +7/-0)
  • extensions/diffs/skills/diffs/SKILL.md (modified, +1/-0)
  • extensions/diffs/src/config.test.ts (modified, +51/-0)
  • extensions/diffs/src/config.ts (modified, +31/-0)
  • extensions/diffs/src/store.test.ts (modified, +8/-0)
  • extensions/diffs/src/tool.test.ts (modified, +53/-1)
  • extensions/diffs/src/tool.ts (modified, +3/-2)
  • extensions/diffs/src/url.ts (modified, +8/-4)

Code Example

/tmp/openclaw/openclaw-diffs/<id>/meta.json/tmp/openclaw/openclaw-diffs/<id>/preview.png/tmp/openclaw/openclaw-diffs/<id>/viewer.html
RAW_BUFFERClick to expand / collapse

Summary

The diffs tool generates viewer URLs that immediately return Diff not found even when called from the host machine via curl. The artifact files are written to disk correctly.

Environment

  • OpenClaw version: v2026.4.1
  • OS: Linux 6.17.0-1009-oracle (arm64)

Steps to Reproduce

  1. Call the diffs tool with any before/after text and mode=both
  2. Tool returns a viewer URL and reports PNG generated
  3. curl http://127.0.0.1:18789/plugins/diffs/view/<id>/<token> immediately returns Diff not found

Evidence

Files are correctly written to disk:

/tmp/openclaw/openclaw-diffs/<id>/meta.json   ✅
/tmp/openclaw/openclaw-diffs/<id>/preview.png ✅
/tmp/openclaw/openclaw-diffs/<id>/viewer.html ✅

meta.json contains correct id, token, viewerPath, and TTL. But the gateway endpoint returns Diff not found immediately — from the host machine itself, ruling out TTL expiry, localhost restriction, or tunnel issues.

Root Cause Hypothesis

The plugin writes artifacts to disk but does not register them in the gateway's in-memory artifact registry. The viewer endpoint does an in-memory lookup only and never falls back to disk.

Workaround

PNG delivery works correctly. Use mode=file or mode=both and send the filePath via the message tool instead of sharing the viewer URL.

Related

Previously tracked as #58598 (missing viewer-runtime.js, resolved in v2026.3.31). This is a distinct regression introduced in or persisting after v2026.4.1.

extent analysis

TL;DR

The diffs tool's viewer URL returns "Diff not found" due to the plugin not registering artifacts in the gateway's in-memory registry, so using mode=file or mode=both and sending the file path via the message tool is a viable workaround.

Guidance

  • Verify that the meta.json file contains the correct id, token, viewerPath, and TTL to ensure that the artifact files are being written correctly to disk.
  • Check the gateway's configuration to see if there are any settings that control how artifacts are registered in the in-memory registry.
  • Use the workaround of sending the filePath via the message tool instead of sharing the viewer URL to deliver the PNG file correctly.
  • Investigate the plugin's code to see why it is not registering the artifacts in the gateway's in-memory registry.

Example

No code snippet is provided as it is not clearly supported by the issue.

Notes

This workaround may not be suitable for all use cases, especially those that rely on the viewer URL. The root cause of the issue is still unknown and needs to be investigated further.

Recommendation

Apply the workaround of using mode=file or mode=both and sending the filePath via the message tool, as it allows for the delivery of the PNG file correctly, although it may not be a permanent solution.

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…

Still need to ship something?

×6

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

Back to top recommendations

TRENDING