Skip to content

fix(memory-wiki): skip bridge prune when capability unavailable but sources intact [AI-assisted]#67711

Closed
neo1027144-creator wants to merge 3 commits into
openclaw:mainfrom
neo1027144-creator:fix/memory-wiki-bridge-prune-guard-67658
Closed

fix(memory-wiki): skip bridge prune when capability unavailable but sources intact [AI-assisted]#67711
neo1027144-creator wants to merge 3 commits into
openclaw:mainfrom
neo1027144-creator:fix/memory-wiki-bridge-prune-guard-67658

Conversation

@neo1027144-creator

Copy link
Copy Markdown
Contributor

AI-assisted: yes (Antigravity / Gemini).

Summary

Fixes #67658.

When syncMemoryWikiBridgeSources runs and listActiveMemoryPublicArtifacts returns an empty array (because the memory plugin capability is temporarily unregistered or unavailable), �ctiveKeys ends up as an empty Set. The subsequent call to pruneImportedSourceEntries then marks every existing group=bridge state entry as stale and deletes the corresponding wiki pages — even though the underlying source files are still on disk.

Root cause

�ridge.ts conflates two distinct situations:

  1. Transient capability outage — plugin not loaded this cycle, source files still exist
  2. Genuine source deletion — artifact removed, source files also gone

Both produce an empty �rtifacts list, but only case 2 should trigger pruning.

Fix

Before calling pruneImportedSourceEntries, check whether the empty artifacts list coincides with existing bridge entries whose source files are still accessible on disk. If at least one source file is reachable via s.access, skip the prune entirely (
emovedCount = 0). When all source files are also gone, pruning proceeds as before.

` s
// Guard: skip pruning when capability is temporarily unavailable but
// source files still exist on disk. (#67658)
const existingBridgeSourcePaths = Object.values(state.entries)
.filter((entry) => entry.group === 'bridge')
.map((entry) => entry.sourcePath);

const shouldSkipPrune =
artifacts.length === 0 &&
existingBridgeSourcePaths.length > 0 &&
(await Promise.any(
existingBridgeSourcePaths.map((p) => fs.access(p).then(() => true)),
).catch(() => false));
`

Tests

Two new regression tests added to �ridge.test.ts:

| Scenario |
emovedCount | Pages on disk |
|---|---|---|
| Capability lost, source files still exist |

@greptile-apps

greptile-apps Bot commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds a guard in syncMemoryWikiBridgeSources (extensions/memory-wiki/src/bridge.ts) to skip pruneImportedSourceEntries when no artifacts are returned but existing bridge state entries still have source files on disk — preventing the transient capability-outage scenario from wiping all bridge wiki pages. Two regression tests cover both the "skip prune when files exist" and "prune when files are also gone" paths.

  • The guard condition artifacts.length === 0 cannot distinguish a transient outage from a legitimate "all artifacts genuinely removed" case. If a user removes all workspaces from their agent config while source files remain on disk, shouldSkipPrune will be true and stale bridge pages will persist indefinitely. This is the known tradeoff of the heuristic; the existing comment should note it explicitly so future maintainers understand why the check cannot be made exact with the current SDK surface.

Confidence Score: 4/5

Safe to merge for the primary bug; the heuristic guard has a known false-negative edge case (stale pages not cleaned up when workspaces are legitimately removed but files remain) that should be documented or followed up.

The fix correctly addresses the critical data-loss scenario (#67658) and is well-tested. The one remaining concern — the guard's inability to distinguish transient outage from legitimate empty results — is a correctness issue (wrong pruning decision in an edge case) that doesn't block the primary fix but warrants acknowledgment before merging.

extensions/memory-wiki/src/bridge.ts lines 259–266 (the shouldSkipPrune condition and its comment)

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/memory-wiki/src/bridge.ts
Line: 259-266

Comment:
**Guard conflates "unavailable capability" with "empty artifact list"**

`artifacts.length === 0` is used as a proxy for "capability temporarily unavailable," but this condition is also true when the capability IS available and legitimately returns zero results — e.g., the user removed all workspaces from `appConfig.agents.list` while source files remain on disk. In that scenario `shouldSkipPrune` becomes `true`, so stale bridge pages are never pruned even though all artifacts are genuinely gone and the prune is correct.

A more precise signal would be whether `listActiveMemoryPublicArtifacts` indicated unavailability (e.g., via a returned flag or a thrown/caught error) rather than inferring it from the result count. With the current SDK surface that may not be possible without an API change, but the comment should at minimum call out this known limitation so future readers understand the tradeoff.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(memory-wiki): skip bridge prune when..." | Re-trigger Greptile

Comment on lines +259 to +266
const shouldSkipPrune =
artifacts.length === 0 &&
existingBridgeSourcePaths.length > 0 &&
(await Promise.any(
existingBridgeSourcePaths.map((sourcePath) =>
fs.access(sourcePath).then(() => true),
),
).catch(() => false));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Guard conflates "unavailable capability" with "empty artifact list"

artifacts.length === 0 is used as a proxy for "capability temporarily unavailable," but this condition is also true when the capability IS available and legitimately returns zero results — e.g., the user removed all workspaces from appConfig.agents.list while source files remain on disk. In that scenario shouldSkipPrune becomes true, so stale bridge pages are never pruned even though all artifacts are genuinely gone and the prune is correct.

A more precise signal would be whether listActiveMemoryPublicArtifacts indicated unavailability (e.g., via a returned flag or a thrown/caught error) rather than inferring it from the result count. With the current SDK surface that may not be possible without an API change, but the comment should at minimum call out this known limitation so future readers understand the tradeoff.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/memory-wiki/src/bridge.ts
Line: 259-266

Comment:
**Guard conflates "unavailable capability" with "empty artifact list"**

`artifacts.length === 0` is used as a proxy for "capability temporarily unavailable," but this condition is also true when the capability IS available and legitimately returns zero results — e.g., the user removed all workspaces from `appConfig.agents.list` while source files remain on disk. In that scenario `shouldSkipPrune` becomes `true`, so stale bridge pages are never pruned even though all artifacts are genuinely gone and the prune is correct.

A more precise signal would be whether `listActiveMemoryPublicArtifacts` indicated unavailability (e.g., via a returned flag or a thrown/caught error) rather than inferring it from the result count. With the current SDK surface that may not be possible without an API change, but the comment should at minimum call out this known limitation so future readers understand the tradeoff.

How can I resolve this? If you propose a fix, please make it concise.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1f193e09f3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extensions/memory-wiki/src/bridge.ts Outdated
.map((entry) => entry.sourcePath);

const shouldSkipPrune =
artifacts.length === 0 &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate prune-skip on capability absence, not filtered artifacts

The new guard uses artifacts.length === 0 to infer a capability outage, but artifacts is post-filtered by bridge config (shouldImportArtifact in collectBridgeArtifacts). If a user disables an index flag (for example indexMemoryRoot: false) while the memory capability is healthy, artifacts becomes empty and this branch skips pruning, leaving previously imported bridge pages and sync-state entries that should be removed. This regresses config-driven cleanup behavior and can keep stale content visible indefinitely.

Useful? React with 👍 / 👎.

@neo1027144-creator

Copy link
Copy Markdown
Contributor Author

Addressed in the latest commit (4720a27): expanded the guard comment to explicitly document this known tradeoff.

The heuristic intentionally favors data preservation over exact correctness: stale pages from a legitimate workspace removal will persist until the next sync cycle where the capability is available. This is acceptable because:

  1. Data loss is irreversible — wrongly deleted pages cannot be recovered without the original artifacts
  2. Stale pages are self-healing — the next successful sync cycle will prune them correctly
  3. The SDK surface doesn't expose an availability signal — \listActiveMemoryPublicArtifacts\ returns []\ for both outage and genuine empty, so we can't resolve this without an upstream API change

…ources intact

When syncMemoryWikiBridgeSources runs with an empty artifacts list (e.g.
the memory capability is temporarily unregistered), activeKeys is an empty
Set. The subsequent pruneImportedSourceEntries call then treats every
existing group=bridge entry as stale and deletes the corresponding wiki
pages, even though the underlying source files are still present on disk.

Fix: before calling pruneImportedSourceEntries, check whether the empty
artifacts list is due to a transient capability outage. When no artifacts
are returned but at least one historical bridge entry still has its source
file accessible on disk, skip the prune entirely (removedCount=0) to avoid
data loss. Pruning still proceeds normally when source files are genuinely
gone.

Adds two regression tests:
- capability lost, source files intact: removedCount=0, pages preserved
- capability lost AND source files deleted: removedCount>0, pages removed

Fixes openclaw#67658
Address Greptile review: explicitly note in the guard comment that
artifacts.length === 0 cannot distinguish a transient capability outage
from a legitimate empty result (e.g. all workspaces removed). Stale
pages may persist in the edge case until the next successful sync.
@neo1027144-creator
neo1027144-creator force-pushed the fix/memory-wiki-bridge-prune-guard-67658 branch from 4720a27 to 46db3c3 Compare April 16, 2026 20:12

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 46db3c3bf7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extensions/memory-wiki/src/bridge.ts Outdated
.map((entry) => entry.sourcePath);

const shouldSkipPrune =
artifacts.length === 0 &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate prune-skip on capability state, not filtered artifacts

The new shouldSkipPrune heuristic uses artifacts.length === 0, but artifacts is already filtered by shouldImportArtifact; this makes normal config choices look like a capability outage. For example, when bridge index flags filter out all artifact kinds while legacy bridge entries still point to existing source files, this branch skips pruning and leaves stale bridge pages/state entries that should be removed to match current config. The outage check should key off an unfiltered signal (for example capability registration or publicArtifacts) instead of the post-filter list.

Useful? React with 👍 / 👎.

The shouldSkipPrune heuristic was checking artifacts.length (post-filter)
which made normal config choices (e.g. disabling all bridge index flags)
look like a capability outage, leaving stale bridge pages unpruned.

Now checks publicArtifacts.length (pre-filter) so that:
- Capability outage (zero raw artifacts + files on disk) → skip prune
- Config filtering (raw artifacts exist but all filtered) → prune normally

Addresses Codex P2 review on openclaw#67711.
@neo1027144-creator

Copy link
Copy Markdown
Contributor Author

Fixed in 88dff87 — the prune-skip guard now checks publicArtifacts.length (unfiltered, pre-shouldImportArtifact) instead of �rtifacts.length (filtered). This correctly distinguishes:

  • Capability outage (zero raw artifacts + source files on disk) → skip prune ✅
  • Config filtering (capability healthy, but all kinds disabled via bridge index flags) → prune normally ✅

Added a regression test that imports with indexMemoryRoot: true, then re-syncs with indexMemoryRoot: false while the capability still returns the artifact — confirms stale pages are pruned.

@clawsweeper

clawsweeper Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Closing this as implemented after Codex automated review.

Current main already fixes the central data-loss path targeted by PR #67711. syncMemoryWikiBridgeSources now gates bridge pruning on active memory capability registration, so an unavailable/unloaded memory capability no longer turns an empty artifact list into deletion of all bridge-imported wiki pages. The PR discussion and review comments were useful, but the remaining PR branch is superseded by the mainline guard.

Best possible solution:

Close PR #67711 as implemented on current main. Keep the mainline capability-registration guard as the product behavior; any desire for the PR branch's narrower source-file-existence heuristic or extra bridge-specific regression coverage should be handled as a new follow-up, not by keeping this stale PR open.

What I checked:

  • PR target and discussion: PR fix(memory-wiki): skip bridge prune when capability unavailable but sources intact [AI-assisted] #67711 explicitly fixes [Bug]: Wiki Bridge deletes all sources when memory plugin artifacts unavailable #67658 and targets the case where listActiveMemoryPublicArtifacts returns an empty list because the memory capability is temporarily unavailable. Review comments on the PR refined the proposed heuristic from filtered artifacts to unfiltered public artifacts, confirming the central concern was avoiding destructive bridge pruning on transient artifact unavailability.
  • Current main skips prune when memory capability is absent: syncMemoryWikiBridgeSources imports getMemoryCapabilityRegistration(), reads the current memory capability, and only calls pruneImportedSourceEntries when a capability is registered; otherwise removedCount is 0. The inline comment anchors this to avoiding removal of all bridge-imported entries when memory-core is not loaded. (extensions/memory-wiki/src/bridge.ts:252, d54d2d6b9b8a)
  • Prune helper is the destructive path being guarded: pruneImportedSourceEntries deletes each stored entry in the target group whose sync key is not in activeKeys. With an empty active set, this would remove all bridge entries unless the bridge-level capability guard skips the call. (extensions/memory-wiki/src/source-sync-state.ts:98, d54d2d6b9b8a)
  • Memory artifact API exposes the guard signal: getMemoryCapabilityRegistration() returns undefined when no memory capability is registered, and listActiveMemoryPublicArtifacts otherwise reads from memoryPluginState.capability?.capability.publicArtifacts?.listArtifacts(...). That is the dependency contract the bridge guard now uses. (src/plugins/memory-state.ts:177, d54d2d6b9b8a)
  • Loaded memory-core still provides public artifacts: Bundled memory-core registers a memory capability with publicArtifacts.listArtifacts, so normal loaded-capability runs still expose artifacts and allow legitimate bridge pruning to proceed. (extensions/memory-core/index.ts:33, d54d2d6b9b8a)
  • Loader tests cover capability preservation: Current tests verify cached memory capability public artifacts survive cache hits and that previously registered memory capability survives activate:false snapshot loads, covering the related capability-loss surface behind the original empty-artifact reports. (src/plugins/loader.test.ts:3508, d54d2d6b9b8a)

So I’m closing this as already implemented rather than keeping a duplicate issue open.

Codex Review notes: model gpt-5.5, reasoning high; reviewed against d54d2d6b9b8a; fix evidence: commit d54d2d6b9b8a.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Wiki Bridge deletes all sources when memory plugin artifacts unavailable

1 participant