openclaw - ✅(Solved) Fix [Bug]: searchMemoryCorpusSupplements uses Promise.all causing single supplement failure to discard all results [2 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#77897Fetched 2026-05-06 06:19:35
View on GitHub
Comments
1
Participants
2
Timeline
3
Reactions
2
Timeline (top)
cross-referenced ×2commented ×1

searchMemoryCorpusSupplements in extensions/memory-core/src/tools.shared.ts uses Promise.all to collect results from registered corpus supplements (e.g. memory-wiki). Promise.all fails fast — if any single supplement's search() rejects, all supplement results are discarded.

Root Cause

searchMemoryCorpusSupplements in extensions/memory-core/src/tools.shared.ts uses Promise.all to collect results from registered corpus supplements (e.g. memory-wiki). Promise.all fails fast — if any single supplement's search() rejects, all supplement results are discarded.

Fix Action

Fixed

PR fix notes

PR #77899: fix(memory-core): use Promise.allSettled in searchMemoryCorpusSupplements

Description (problem / solution / changelog)

Summary

searchMemoryCorpusSupplements uses Promise.all which fails fast — a single supplement failure discards ALL results. Switch to Promise.allSettled so surviving results are preserved.

  • Problem: Promise.all rejects on the first failure, discarding results from healthy supplements.
  • Why it matters: With corpus=all, a single misbehaving supplement (corrupted wiki vault, network error) causes all supplement results to be lost. Future supplement registrations multiply the blast radius.
  • What changed: Promise.allPromise.allSettled + .flatMap(r => r.status === "fulfilled" ? r.value : [])
  • What did NOT change: The sorting, slicing, flat-mapping logic. Caller behavior unchanged.

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

  • Closes #77897

Root Cause

Promise.all is fail-fast by design. When used to aggregate results from N independent corpus supplements, a single rejection aborts all in-flight searches.

Regression Test Plan

  • Coverage level: Unit test
  • Target: extensions/memory-core/src/tools.allsettled.test.ts (inline verification)
  • If no new test is added, why not: The 5-case scenario test below proves the behavior. Existing extensions/memory-wiki/src/query.test.ts (20 tests) continues to pass.

User-visible / Behavior Changes

None. Successful searches return the same results. The only difference is that when one supplement fails, results from other supplements are no longer lost.

Security Impact

  • 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

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: Previously, a supplement failure was noisy (threw). Now it's silent (failed supplement is just skipped).
    • Mitigation: This is the desired behavior — a single supplement should not take down memory search. Supplement authors should add their own error logging.

Real behavior proof (required for external PRs)

  • Behavior or issue addressed: Promise.all fail-fast discards all supplement results when one fails. Promise.allSettled isolates failures.
  • Real environment tested: macOS 15.x, Node 22, OpenClaw main branch with the 3-line fix applied.
  • Exact steps or command run after this patch:
    pnpm test extensions/memory-wiki/src/query.test.ts --run
    Plus inline behavior verification with 5 scenario tests.
  • Evidence after fix:

All 20 existing wiki query tests pass:

 Test Files  1 passed (1)
      Tests  20 passed (20)
   Duration  617ms

Inline Promise.allSettled behavior proof — 5/5 scenarios pass, including the critical case:

 ✓ returns results from all successful supplements
 ✓ returns results from successful supplements even when one fails   ← ★ fix
 ✓ returns empty array when all supplements fail
 ✓ handles empty supplements array
 ✓ use case: wiki success, memory-lancedb supplement fails
  • Observed result after fix: When one supplement throws, results from the other survive (was: all discarded). When all fail, empty array returned (same as before). When all succeed, behavior identical.
  • What was not tested: Integration with live LanceDB or a real corrupted wiki vault. The Promise.allSettled behavior is a JS runtime primitive — the fix is the API swap itself.
  • Before evidence (Promise.all): await Promise.all([...supplements.map(search)]) — any single rejection → entire call rejects, no partial results.

Changed files

  • extensions/memory-core/src/tools.shared.ts (modified, +3/-3)

PR #78035: fix(memory-core): preserve sibling supplement results when one search rejects (#77897)

Description (problem / solution / changelog)

Summary

Fixes #77897. searchMemoryCorpusSupplements (extensions/memory-core/src/tools.shared.ts) used Promise.all to fan out to every registered corpus supplement (memory-wiki, third-party plugin supplements, etc.). Promise.all is fail-fast: a single rejection discards every sibling's already-resolved results.

This patch swaps Promise.allPromise.allSettled, then re-flattens the fulfilled values. Rejections are logged once per offender with the registering plugin id so operators can correlate to the responsible supplement.

Behavior contract (mathematical guarantee)

Let S be the set of registered corpus supplements at call time. Let S_ok ⊆ S be those whose search(params) fulfills, and S_err = S \ S_ok those that reject. The function now satisfies:

result = sort_score_desc_then_path(⋃_{s ∈ S_ok} s.search(params)).slice(0, max(1, maxResults ?? 10))

In particular:

  • |S_err| ≥ 1 does not collapse result to []. Sibling results survive.
  • |S_err| = |S| returns [] (graceful degradation, not throw).
  • |S_err| = 0 is byte-identical to the prior behavior — same merge, same sort, same maxResults clamp.

Real behavior proof

7 new tests in extensions/memory-core/src/tools.shared.test.ts lock in the contract:

TestInvariant verified
empty registry → []base case, no fanout
1 good + 1 throws → returns 1S_err=1 does not poison S_ok
all reject → [] (no throw)full degradation is graceful
2 good → merged, sorted by score desc, path ascmerge + ordering preserved
maxResults: 5, maxResults: 0top-K clamp + ≥1 floor preserved
corpus: "memory" | "sessions"early-return short-circuits without calling supplements
non-Error rejection (string, object)rejection reason serialization is robust
$ pnpm test extensions/memory-core/src/tools.shared.test.ts
Test Files  1 passed (1)
     Tests  7 passed (7)

Why this matters

Memory search is on the agent's hot read path. Before this fix, a single misbehaving wiki/corpus plugin could cause all memory_search(corpus="all") calls to look empty — the agent silently loses retrieval signal, and the bad plugin is hard to identify because results just disappear. After this fix, the failing plugin is named in the warn log and sibling retrieval continues to work, so a noisy supplement degrades gracefully instead of poisoning the whole memory subsystem.

Risk

  • Surface: one function in one bundled extension. Get-by-lookup is unchanged (it iterates synchronously and returns the first hit, so a single throw was already isolated).
  • Compat: Promise.allSettled is supported in Node 22+ (the repo's stated runtime).
  • Logging: one console.warn per failing supplement per call; matches existing memory-core: log prefix used in dreaming-narrative.ts.

Closes #77897

🤖 Generated with Claude Code

Changed files

  • extensions/memory-core/src/tools.shared.test.ts (added, +183/-0)
  • extensions/memory-core/src/tools.shared.ts (modified, +31/-5)
RAW_BUFFERClick to expand / collapse

Summary

searchMemoryCorpusSupplements in extensions/memory-core/src/tools.shared.ts uses Promise.all to collect results from registered corpus supplements (e.g. memory-wiki). Promise.all fails fast — if any single supplement's search() rejects, all supplement results are discarded.

Steps to reproduce

  1. Register two corpus supplements (e.g. memory-wiki + a misbehaving one)
  2. Call memory_search(corpus="all")
  3. If one supplement throws, observe that results from the OTHER supplement are also lost

Expected behavior

A failure in one supplement should not affect results from other supplements. The surviving results should still be returned.

Actual behavior

Promise.all rejects immediately when the first supplement fails, discarding all other in-flight results.

OpenClaw version

main branch

Operating system

N/A

Model

N/A

Provider / routing chain

N/A

extent analysis

TL;DR

Use Promise.allSettled instead of Promise.all to collect results from registered corpus supplements, allowing successful results to be returned even if some supplements fail.

Guidance

  • Identify the searchMemoryCorpusSupplements function in extensions/memory-core/src/tools.shared.ts and replace Promise.all with Promise.allSettled to handle rejections individually.
  • Verify that the function now returns an array of objects with status and value or reason properties, indicating the outcome of each supplement's search() call.
  • Modify the result processing logic to filter out failed supplements and return the successful results.
  • Consider adding error handling or logging for failed supplements to improve debugging and monitoring.

Example

const results = await Promise.allSettled(supplements.map(supplement => supplement.search()));
const successfulResults = results.filter(result => result.status === 'fulfilled').map(result => result.value);

Notes

This solution assumes that the search() method of each supplement returns a promise. If the supplements use a different asynchronous pattern, additional modifications may be necessary.

Recommendation

Apply workaround: Replace Promise.all with Promise.allSettled to handle rejections individually and return successful results. This approach allows for more robust error handling and provides a better user experience by returning available results even if some supplements fail.

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

A failure in one supplement should not affect results from other supplements. The surviving results should still be returned.

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]: searchMemoryCorpusSupplements uses Promise.all causing single supplement failure to discard all results [2 pull requests, 1 comments, 2 participants]