openclaw - ✅(Solved) Fix Single card hydration failure aborts all remaining diff cards on the page [1 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#83914Fetched 2026-05-20 03:46:49
View on GitHub
Comments
1
Participants
2
Timeline
8
Reactions
1
Timeline (top)
labeled ×5commented ×1cross-referenced ×1unsubscribed ×1

Error Message

The loop in hydrateViewer constructs a FileDiff and calls diff.hydrate() for each card in sequence without any per-card try/catch. If any card's new FileDiff(...) constructor or diff.hydrate() call throws — for instance due to a malformed prerenderedHTML, missing shadow root, or an upstream @pierre/diffs assertion — the thrown error propagates to hydrateViewer(), which rejects. main() catches that rejection and marks the entire page with dataset.openclawDiffsError = "true", but all subsequent cards are left unhydrated. On a page with N diff cards, a defect in card K silently swallows cards K+1 through N with no per-card error reporting. Embed two .oc-diff-card elements on the page where the first card has a corrupted data-openclaw-diff-payload that survives parsePayload but causes FileDiff.hydrate() to throw. The second card will never be hydrated, and no per-card error signal is emitted. Wrap the per-card body in a try/catch so a single failing card logs a warning and continues to the next: try { ensureShadowRoot(host); const diff = new FileDiff(...); diff.hydrate({...}); controllers.push({payload, diff}); applyState(controller); } catch (err) { console.warn('Failed to hydrate diff card', err); }

Fix Action

Fix / Workaround

Severity: medium / Confidence: high / Category: bug Triage: confirmed-bug Detected against: openclaw v2026.5.18 (latest stable at time of scan, 2026-05-18) Tooling: clawpatch 0.3.0 + acpx/claude-sonnet-4-5 via Brad Mills protocol


Standardized clawpatch finding. Persistent in v2026.5.18 (not resolved by upgrading from v2026.5.12). Finding ID: fnd_sig-feat-cli-command-1f351e9797-_32e7e0adc5.

PR fix notes

PR #84263: fix(diffs): isolate per-card hydration failures in viewer-client (#83914)

Description (problem / solution / changelog)

Fixes #83914.

The hydration loop in hydrateViewer (extensions/diffs/src/viewer-client.ts:321-332) constructed new FileDiff(createRenderOptions(payload)) and invoked diff.hydrate({...}) for each .oc-diff-card host in sequence with no per-card try/catch. A single corrupted prerenderedHTML payload, missing shadow root, or upstream @pierre/diffs assertion threw out of the loop, propagated up to main(), was caught as a page-wide failure (dataset.openclawDiffsError = "true"), and left every card after the failed one unhydrated. On a page with N diff cards, a defect in card K silently swallowed cards K+1 through N — no per-card error signal, no partial hydration recovery.

Changes

  • extensions/diffs/src/viewer-client.ts: wrap the per-card body inside hydrateViewer in try { … } catch (err) { host.dataset.openclawDiffsCardError = "true"; console.warn("Failed to hydrate diff card", err); }. The catch tags the failing host with an observable dataset marker (so callers and tests can detect partial hydration without losing the rest of the page) and surfaces the underlying error via console.warn. The outer main() try/catch is unchanged — it still owns truly fatal failures like preloadHighlighter throwing.

Diff stat: 1 file, +23 / -10. Behavior change is strictly more permissive: any case that previously hydrated successfully still does so identically; cases that previously threw now isolate the failure to the failing card.

Real behavior proof

  • Behavior or issue addressed: Sanitized issue evidence — the loop body at viewer-client.ts:321-332 constructs FileDiff per card and calls diff.hydrate. FileDiff's constructor and hydrate method both come from @pierre/diffs and can throw on malformed input. The per-card scope of the failure was previously not respected.

  • Real environment tested: Local Node 22.x. Probe at /tmp/probe_83914.mjs does both halves of the proof. (a) Parses the patched viewer-client.ts and verifies the per-card body is wrapped in try { … } catch (err) { … }, that the catch tags the host with dataset.openclawDiffsCardError, and that it logs via console.warn. (b) Replays the loop semantics in pure JS for four scenarios — buggy shape (card A throws → loop propagates → B and C never hydrate, confirming the #83914 symptom), patched shape (card A throws → A is tagged with the error marker, B and C still hydrate via the controllers list), all-healthy regression (3 cards all hydrate, no failure markers), and middle-card failure (A and C hydrate around B's failure).

  • Exact steps or command run after this patch: node /tmp/probe_83914.mjs

  • Evidence after fix:

PASS: per-card hydration block is wrapped in try { … } catch (err) { … }
PASS: failed host is tagged with openclawDiffsCardError dataset marker for observability
PASS: failure is surfaced via console.warn with the underlying error
PASS: replay (buggy): card A throws → loop aborts → B and C never hydrate (confirms #83914)
PASS: replay (patched): A failure isolated; B and C hydrate; A's host tagged with error marker
PASS: replay (patched, all healthy): all 3 cards hydrate, no failure markers — no regression
PASS: replay (patched, middle card fails): A and C still hydrate around B's failure
  • Observed result after fix: A page with multiple .oc-diff-card hosts where one card has corrupted payload now hydrates the remaining cards normally. The failing card carries data-openclaw-diffs-card-error="true" for inspection. The page-level dataset.openclawDiffsReady = "true" continues to be set because hydrateViewer no longer rejects — matching the issue's "Suggested regression test" expectation.

  • What was not tested: A real DOM environment with @pierre/diffs running — the issue notes "no DOM-level unit tests for viewer-client.ts that exercise multi-card hydration failure scenarios" exist in the repo, and standing one up is outside the scope of a per-card isolation fix. The probe replays the exact loop-vs-throw shape at the call site.

Audit (per CLAUDE rules — all 5 steps)

  • Existing-helper check: No helper introduced — a single try/catch block. The dataset marker openclawDiffsCardError follows the same convention as the existing page-level openclawDiffsError / openclawDiffsReady markers in main(). PASS
  • Shared-helper caller check: hydrateViewer is called only from main() in the same file. The new error-isolation behavior makes hydrateViewer strictly more permissive — any caller that previously saw it resolve still does. PASS
  • Broader-fix rival scan: gh pr list --search '83914 in:title,body' and gh pr list --search 'hydrateViewer in:title,body' return no open PRs. Issue timeline shows zero cross-references. PASS
  • Recent-merge audit: git log --oneline -5 -- extensions/diffs/src/viewer-client.ts shows no recent commits touching this file. PASS
  • Prototype-pollution scan: N/A — pure try/catch with a dataset assignment on a known DOM HTMLElement.

Changed files

  • extensions/diffs/src/viewer-client.ts (modified, +23/-10)

Code Example

for (const { host, payload } of cards) {
    ensureShadowRoot(host);
    const diff = new FileDiff(createRenderOptions(payload));
    diff.hydrate({
      fileContainer: host,
      prerenderedHTML: payload.prerenderedHTML,
      ...getHydrateProps(payload),
    });
    const controller = { payload, diff };
    controllers.push(controller);
    applyState(controller);
  }
RAW_BUFFERClick to expand / collapse

Severity: medium / Confidence: high / Category: bug Triage: confirmed-bug Detected against: openclaw v2026.5.18 (latest stable at time of scan, 2026-05-18) Tooling: clawpatch 0.3.0 + acpx/claude-sonnet-4-5 via Brad Mills protocol

Evidence

  • extensions/diffs/src/viewer-client.ts:297-310 (hydrateViewer)
for (const { host, payload } of cards) {
    ensureShadowRoot(host);
    const diff = new FileDiff(createRenderOptions(payload));
    diff.hydrate({
      fileContainer: host,
      prerenderedHTML: payload.prerenderedHTML,
      ...getHydrateProps(payload),
    });
    const controller = { payload, diff };
    controllers.push(controller);
    applyState(controller);
  }

Reasoning

The loop in hydrateViewer constructs a FileDiff and calls diff.hydrate() for each card in sequence without any per-card try/catch. If any card's new FileDiff(...) constructor or diff.hydrate() call throws — for instance due to a malformed prerenderedHTML, missing shadow root, or an upstream @pierre/diffs assertion — the thrown error propagates to hydrateViewer(), which rejects. main() catches that rejection and marks the entire page with dataset.openclawDiffsError = "true", but all subsequent cards are left unhydrated. On a page with N diff cards, a defect in card K silently swallows cards K+1 through N with no per-card error reporting.

Reproduction

Embed two .oc-diff-card elements on the page where the first card has a corrupted data-openclaw-diff-payload that survives parsePayload but causes FileDiff.hydrate() to throw. The second card will never be hydrated, and no per-card error signal is emitted.

Recommendation

Wrap the per-card body in a try/catch so a single failing card logs a warning and continues to the next: try { ensureShadowRoot(host); const diff = new FileDiff(...); diff.hydrate({...}); controllers.push({payload, diff}); applyState(controller); } catch (err) { console.warn('Failed to hydrate diff card', err); }

Why existing tests miss this

All tests for the viewer runtime operate through the built bundle content check (config.test.ts line ~386) or integration tests against the HTTP layer and plugin API. There are no DOM-level unit tests for viewer-client.ts that exercise multi-card hydration failure scenarios.

Suggested regression test

In a jsdom/happy-dom test environment, inject two .oc-diff-card elements and mock FileDiff.hydrate to throw on the first invocation. Assert that main() resolves without setting openclawDiffsError, and that the second card's controller is present in controllers.

Minimum fix scope

Wrap the per-card hydration block inside hydrateViewer in an individual try/catch. The outer try/catch in main() can remain for truly fatal failures (e.g., preloadHighlighter throwing).


Standardized clawpatch finding. Persistent in v2026.5.18 (not resolved by upgrading from v2026.5.12). Finding ID: fnd_sig-feat-cli-command-1f351e9797-_32e7e0adc5.

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