Summary
There is no single hook that fires before a session ends, regardless of how it ends. Auto-compaction runs memoryFlush. Manual /compact, /new, and /reset do not. The before_compaction hook exists in the type system but is never called. Plugin authors building memory persistence have no reliable way to save state before context is destroyed.
This isn't one bug — it's a pattern across multiple code paths.
Related Issues
The first three describe the same root cause: manual session-ending commands bypass the memory persistence infrastructure that auto-compaction uses.
#12590 outlines why memoryFlush does not fire at every auto-compact.
The Problem
The memoryFlush config works correctly for auto-compaction:
"compaction": {
"memoryFlush": {
"enabled": true,
"prompt": "Save important context before compaction..."
}
}
runMemoryFlushIfNeeded() in agent-runner-memory.ts handles this path.
But every manual path skips it:
| Trigger |
Runs memoryFlush? |
Fires before_compaction hook? |
| Auto-compaction (token threshold) |
Yes |
No |
/compact |
No |
No |
/new |
No |
No |
/reset |
No |
No |
The before_compaction hook (runBeforeCompaction() in plugins/hooks.ts line 216, typed in plugins/types.ts lines 290, 470) has zero call sites anywhere. It's dead code.
What Plugin Authors Actually Need
One hook. Fires before any session-ending or context-destroying event. Doesn't matter if the user typed /compact, /new, /reset, or the token limit was hit.
Something like:
hooks: {
before_session_end: async (context) => {
// context.reason: 'compact' | 'new' | 'reset' | 'auto_compact'
// Save state, flush memory, persist to external stores
}
}
This lets plugins do the right thing without needing to know every possible code path that destroys context.
Suggested Fix
Minimum viable fix:
- Call
runMemoryFlushIfNeeded() before compactEmbeddedPiSession() in commands-compact.ts
- Do the same in the
/new and /reset handlers
- Wire
runBeforeCompaction() into all compaction paths, or remove it from the type definitions
Better fix:
- Introduce a
before_session_end hook that dispatches from every path that destroys or compresses context
- Pass the trigger reason so plugins can decide how to respond
- Deprecate the unwired
before_compaction / after_compaction hooks or make them aliases
Impact
Any plugin relying on memoryFlush or before_compaction to persist context to external stores (memory systems, knowledge bases, logging) silently loses data on every manual command. Users have no indication their save-before-compact config is being ignored.
This is particularly painful for plugins that manage persistent memory across sessions, the entire value proposition breaks when 3 out of 4 session-ending paths skip the save step.
Versions
Observed in OpenClaw 2026.02.6, confirmed via source audit of:
src/auto-reply/reply/commands-compact.ts
src/auto-reply/reply/agent-runner-memory.ts
src/auto-reply/reply/memory-flush.ts
src/agents/pi-embedded-runner/compact.ts
src/plugins/hooks.ts
src/plugins/types.ts
Summary
There is no single hook that fires before a session ends, regardless of how it ends. Auto-compaction runs
memoryFlush. Manual/compact,/new, and/resetdo not. Thebefore_compactionhook exists in the type system but is never called. Plugin authors building memory persistence have no reliable way to save state before context is destroyed.This isn't one bug — it's a pattern across multiple code paths.
Related Issues
before_compaction/after_compactionhooks exist in types but have no call sites/newand/resetdon't trigger memory flush (filed Feb 3, 2026)memoryFlushdoes not fire reliably #12590 -memoryFlushdoes not fire reliablyThe first three describe the same root cause: manual session-ending commands bypass the memory persistence infrastructure that auto-compaction uses.
#12590 outlines why memoryFlush does not fire at every auto-compact.
The Problem
The
memoryFlushconfig works correctly for auto-compaction:runMemoryFlushIfNeeded()inagent-runner-memory.tshandles this path.But every manual path skips it:
/compact/new/resetThe
before_compactionhook (runBeforeCompaction()inplugins/hooks.tsline 216, typed inplugins/types.tslines 290, 470) has zero call sites anywhere. It's dead code.What Plugin Authors Actually Need
One hook. Fires before any session-ending or context-destroying event. Doesn't matter if the user typed
/compact,/new,/reset, or the token limit was hit.Something like:
This lets plugins do the right thing without needing to know every possible code path that destroys context.
Suggested Fix
Minimum viable fix:
runMemoryFlushIfNeeded()beforecompactEmbeddedPiSession()incommands-compact.ts/newand/resethandlersrunBeforeCompaction()into all compaction paths, or remove it from the type definitionsBetter fix:
before_session_endhook that dispatches from every path that destroys or compresses contextbefore_compaction/after_compactionhooks or make them aliasesImpact
Any plugin relying on
memoryFlushorbefore_compactionto persist context to external stores (memory systems, knowledge bases, logging) silently loses data on every manual command. Users have no indication their save-before-compact config is being ignored.This is particularly painful for plugins that manage persistent memory across sessions, the entire value proposition breaks when 3 out of 4 session-ending paths skip the save step.
Versions
Observed in OpenClaw 2026.02.6, confirmed via source audit of:
src/auto-reply/reply/commands-compact.tssrc/auto-reply/reply/agent-runner-memory.tssrc/auto-reply/reply/memory-flush.tssrc/agents/pi-embedded-runner/compact.tssrc/plugins/hooks.tssrc/plugins/types.ts