Skip to content

fix(memory-core): prevent dreaming-narrative session leaks (#66358)#67023

Merged
jalehman merged 6 commits intoopenclaw:mainfrom
chiyouYCH:fix/dreaming-session-leak-66358
Apr 20, 2026
Merged

fix(memory-core): prevent dreaming-narrative session leaks (#66358)#67023
jalehman merged 6 commits intoopenclaw:mainfrom
chiyouYCH:fix/dreaming-session-leak-66358

Conversation

@chiyouYCH
Copy link
Copy Markdown
Contributor

Problem

memory-core creates dreaming-narrative-* sub-sessions during the Dreaming sweep but they accumulate indefinitely because the deleteSession call in the finally block uses a different session key than what was created.

Root Cause

In runDreamingSweepPhases, params.nowMs is passed through to runLightDreaming / runRemDreaming without normalization. Each function recomputes Date.now() independently:

// runDreamingSweepPhases
await runLightDreaming({ ..., nowMs: params.nowMs }); // may be undefined

// runLightDreaming
const nowMs = Number.isFinite(params.nowMs) ? params.nowMs : Date.now(); // new timestamp

// generateAndAppendDreamNarrative
const sessionKey = `dreaming-narrative-${phase}-${nowMs}`; // yet another timestamp

The session key written at creation and the key used in deleteSession can differ by milliseconds, so the deletion hits a non-existent key and the session leaks.

Fix (2 files)

extensions/memory-core/src/dreaming-phases.ts

  1. Normalize nowMs once at the top of runDreamingSweepPhasessweepNowMs
  2. Pass consistent sweepNowMs to all phases
  3. Add defensive deleteSession call after each phase (safety net)

extensions/memory-core/src/dreaming-narrative.ts

  1. Guard the finally block with if (params.subagent) to prevent TypeError if the subagent runtime becomes unavailable mid-flight

Testing

Manual trigger of Dreaming sweep on a heavily-polluted system (251 leaking sessions) reduced the count to 0. Subsequent sweeps produce no new leaked sessions.


Full diff: chiyouYCH#1
Files changed: extensions/memory-core/src/dreaming-phases.ts, extensions/memory-core/src/dreaming-narrative.ts

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 15, 2026

Greptile Summary

This PR fixes the root cause of dreaming-narrative-* sub-session leaks by normalizing nowMs once to sweepNowMs in runDreamingSweepPhases, ensuring consistent timestamps flow through to generateAndAppendDreamNarrative. The if (params.subagent) guard added to the finally block in dreaming-narrative.ts is also correct and unproblematic.

  • The defensive deleteSession calls added at the runDreamingSweepPhases level (lines 1645 and 1669) construct keys as dreaming-narrative-{phase}-{sweepNowMs}, but buildNarrativeSessionKey always produces dreaming-narrative-{phase}-{workspaceHash}-{nowMs}. The workspace hash is missing, so both "safety net" deletions always target non-existent keys and silently no-op, leaving the intended redundant cleanup layer broken.

Confidence Score: 3/5

The primary timestamp-normalization fix is correct, but the two new defensive cleanup calls use the wrong session key format and will never delete anything.

Two P1 findings: the safety-net deleteSession calls in runDreamingSweepPhases omit the workspace hash, so they construct keys that don't match those created by buildNarrativeSessionKey. The defensive cleanup is entirely non-functional. This doesn't reintroduce the original leak (the primary finally-block cleanup in generateAndAppendDreamNarrative is correct), but it means the stated 'defensive cleanup' goal of the PR is unmet.

extensions/memory-core/src/dreaming-phases.ts — lines 1645 and 1669 (wrong session key format in defensive cleanup)

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/memory-core/src/dreaming-phases.ts
Line: 1645-1650

Comment:
**Defensive cleanup uses wrong session key format**

The defensive `deleteSession` calls construct keys like `dreaming-narrative-light-${sweepNowMs}`, but `buildNarrativeSessionKey` produces keys in the format `dreaming-narrative-${phase}-${workspaceHash}-${nowMs}`. The workspace hash segment is absent, so these calls always target a non-existent key — the safety net silently does nothing. The same mismatch exists for the REM cleanup at line 1669.

To fix, export `buildNarrativeSessionKey` from `dreaming-narrative.ts` (or re-inline the hash logic here) so the defensive cleanup constructs a key that actually matches what was created.

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

---

This is a comment left during a code review.
Path: extensions/memory-core/src/dreaming-phases.ts
Line: 1669-1674

Comment:
**Same wrong key format for REM cleanup**

`dreaming-narrative-rem-${sweepNowMs}` is missing the workspace hash, so this `deleteSession` call targets a non-existent key and the cleanup is a no-op, identical to the issue on the light-phase cleanup above.

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

Reviews (1): Last reviewed commit: "fix(memory-core): prevent dreaming-narra..." | Re-trigger Greptile

Comment on lines +1645 to +1650
const lightSessionKey = `dreaming-narrative-light-${sweepNowMs}`;
await params.subagent
.deleteSession({ sessionKey: lightSessionKey })
.catch(() => {
// Swallow errors — this is best-effort cleanup.
});
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.

P1 Defensive cleanup uses wrong session key format

The defensive deleteSession calls construct keys like dreaming-narrative-light-${sweepNowMs}, but buildNarrativeSessionKey produces keys in the format dreaming-narrative-${phase}-${workspaceHash}-${nowMs}. The workspace hash segment is absent, so these calls always target a non-existent key — the safety net silently does nothing. The same mismatch exists for the REM cleanup at line 1669.

To fix, export buildNarrativeSessionKey from dreaming-narrative.ts (or re-inline the hash logic here) so the defensive cleanup constructs a key that actually matches what was created.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/memory-core/src/dreaming-phases.ts
Line: 1645-1650

Comment:
**Defensive cleanup uses wrong session key format**

The defensive `deleteSession` calls construct keys like `dreaming-narrative-light-${sweepNowMs}`, but `buildNarrativeSessionKey` produces keys in the format `dreaming-narrative-${phase}-${workspaceHash}-${nowMs}`. The workspace hash segment is absent, so these calls always target a non-existent key — the safety net silently does nothing. The same mismatch exists for the REM cleanup at line 1669.

To fix, export `buildNarrativeSessionKey` from `dreaming-narrative.ts` (or re-inline the hash logic here) so the defensive cleanup constructs a key that actually matches what was created.

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

Comment on lines +1669 to +1674
const remSessionKey = `dreaming-narrative-rem-${sweepNowMs}`;
await params.subagent
.deleteSession({ sessionKey: remSessionKey })
.catch(() => {
// Swallow errors — this is best-effort cleanup.
});
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.

P1 Same wrong key format for REM cleanup

dreaming-narrative-rem-${sweepNowMs} is missing the workspace hash, so this deleteSession call targets a non-existent key and the cleanup is a no-op, identical to the issue on the light-phase cleanup above.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/memory-core/src/dreaming-phases.ts
Line: 1669-1674

Comment:
**Same wrong key format for REM cleanup**

`dreaming-narrative-rem-${sweepNowMs}` is missing the workspace hash, so this `deleteSession` call targets a non-existent key and the cleanup is a no-op, identical to the issue on the light-phase cleanup above.

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

Copy link
Copy Markdown

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

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: 6a9b4b7365

ℹ️ 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".

// Defensive cleanup: ensure the light-phase narrative session is deleted even if
// generateAndAppendDreamNarrative's primary cleanup was skipped due to an error.
if (params.subagent) {
const lightSessionKey = `dreaming-narrative-light-${sweepNowMs}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use canonical narrative session key in fallback cleanup

The defensive deleteSession key here is constructed as dreaming-narrative-light-${sweepNowMs}, but narrative sessions are created with buildNarrativeSessionKey(...) in dreaming-narrative.ts, which includes a workspace hash (dreaming-narrative-<phase>-<workspaceHash>-<nowMs>). In any run where the primary in-function cleanup does not remove the session (the exact case this block is meant to cover), this fallback will always target the wrong key and leave the leaked session behind; the same mismatch is repeated for REM cleanup.

Useful? React with 👍 / 👎.

@jalehman jalehman self-assigned this Apr 16, 2026
@jalehman jalehman force-pushed the fix/dreaming-session-leak-66358 branch from b3cd733 to c11d14f Compare April 16, 2026 19:08
@jalehman jalehman force-pushed the fix/dreaming-session-leak-66358 branch 7 times, most recently from 3805afb to 88964cc Compare April 20, 2026 22:32
chiyouYCH and others added 6 commits April 20, 2026 15:39
…enclaw#66358)

Root cause: `runDreamingSweepPhases` passed `params.nowMs` through to each
phase without normalizing it first. Each phase recomputed `Date.now()` at
call time, producing a slightly different timestamp than the session key
used by `generateAndAppendDreamNarrative`. The resulting session key
mismatch meant the `deleteSession` call cleaned up a different (or
non-existent) key than the one that was created.

Fix:
- Normalize `nowMs` once at the top of `runDreamingSweepPhases` and
  pass the consistent value to all phases.
- Add a defensive `deleteSession` call in `runDreamingSweepPhases` for
  each phase after `runLightDreaming`/`runRemDreaming` completes. This
  acts as a safety net even if the narrative function's primary cleanup
  is skipped.
- Guard the `finally` block in `generateAndAppendDreamNarrative` with
  `if (params.subagent)` to prevent TypeError if the subagent runtime
  becomes unavailable mid-flight.

This fixes the observed session list pollution where
`dreaming-narrative-*-<timestamp>` entries accumulated indefinitely.
@jalehman jalehman force-pushed the fix/dreaming-session-leak-66358 branch from 88964cc to 51f72b2 Compare April 20, 2026 22:40
@jalehman jalehman merged commit 2055e75 into openclaw:main Apr 20, 2026
11 checks passed
@jalehman
Copy link
Copy Markdown
Contributor

Merged via squash.

Thanks @chiyouYCH!

loongfay pushed a commit to yb-claw/openclaw that referenced this pull request Apr 21, 2026
…66358) (openclaw#67023)

Merged via squash.

Prepared head SHA: 51f72b2
Co-authored-by: chiyouYCH <26790612+chiyouYCH@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants