Skip to content

fix(memory): handle zero-initialization in flush compaction dedup#66845

Closed
wkeything wants to merge 2 commits into
openclaw:mainfrom
wkeything:fix/memory-flush-dedup-edge-case
Closed

fix(memory): handle zero-initialization in flush compaction dedup#66845
wkeything wants to merge 2 commits into
openclaw:mainfrom
wkeything:fix/memory-flush-dedup-edge-case

Conversation

@wkeything

Copy link
Copy Markdown

Summary

  • Fixes edge case in hasAlreadyFlushedForCurrentCompaction() where zero-initialized memoryFlushCompactionCount caused memory flush to be incorrectly skipped
  • When memoryFlushCompactionCount was explicitly 0 (from serialization round-trip) but compactionCount was undefined (never compacted), the ?? 0 fallback made both equal, returning true ("already flushed") even though no flush occurred
  • This could cause missed memory entries during the first compaction cycle

Changes

  • Replaced ?? 0 fallback with explicit typeof checks — both fields must be explicit numbers before comparing
  • If either is undefined, returns false (not yet flushed)
  • Added 6 focused regression tests covering zero-initialization, fresh session, explicit zeros, and cycle advancement

Test plan

  • All reply-state tests pass
  • Zero-initialization edge case returns false (flush needed)
  • Both explicitly 0 returns true (legitimate flush at cycle 0)

🤖 Generated with Claude Code

When compactionCount is undefined (session never compacted) but
memoryFlushCompactionCount is explicitly 0, the dedup check
incorrectly reported "already flushed" because undefined ?? 0
matched the flush counter. Now require compactionCount to be an
explicit number before comparing, preventing duplicate memory
entries on the initial compaction cycle.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@greptile-apps

greptile-apps Bot commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes a real edge case in hasAlreadyFlushedForCurrentCompaction where zero-initialized memoryFlushCompactionCount combined with an undefined compactionCount caused the function to wrongly return true, potentially skipping the first-cycle memory flush. The replacement typeof guards are correct and the comment explains the ambiguity clearly.

The new test suite is solid; there are a couple of duplicate cases (see inline comment) that are P2 style cleanup only.

Confidence Score: 5/5

Safe to merge — the fix is correct, well-explained, and the only remaining findings are duplicate test cases.

All findings are P2 (duplicate tests). The logic change itself is straightforward and correct, the comment documents the reasoning, and the test coverage is comprehensive.

No files require special attention.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/reply-state.test.ts
Line: 367-396

Comment:
**Duplicate test cases**

The two tests at lines 367–374 and 389–396 are identical — both assert that `{ compactionCount: 0, memoryFlushCompactionCount: 0 }` returns `true`. One of them can be removed to avoid confusion about whether there are two distinct scenarios being covered.

Similarly, the tests at lines 376–378 and 380–387 both assert that two `undefined` fields return `false` (the only difference is `{}` vs explicit `undefined` values, which behave the same way for optional fields). Consider collapsing those into a single test.

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

Reviews (1): Last reviewed commit: "fix(memory): handle zero-initialization ..." | Re-trigger Greptile

Comment on lines 367 to 396
it("returns true when both compactionCount and memoryFlushCompactionCount are explicitly 0", () => {
expect(
hasAlreadyFlushedForCurrentCompaction({
compactionCount: 0,
memoryFlushCompactionCount: 0,
}),
).toBe(true);
});

it("returns false when both compactionCount and memoryFlushCompactionCount are undefined (fresh session)", () => {
expect(hasAlreadyFlushedForCurrentCompaction({})).toBe(false);
});

it("returns false when compactionCount is undefined and memoryFlushCompactionCount is undefined", () => {
expect(
hasAlreadyFlushedForCurrentCompaction({
compactionCount: undefined,
memoryFlushCompactionCount: undefined,
}),
).toBe(false);
});

it("correctly detects flush at cycle 0 when compactionCount is explicit", () => {
expect(
hasAlreadyFlushedForCurrentCompaction({
compactionCount: 0,
memoryFlushCompactionCount: 0,
}),
).toBe(true);
});

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 Duplicate test cases

The two tests at lines 367–374 and 389–396 are identical — both assert that { compactionCount: 0, memoryFlushCompactionCount: 0 } returns true. One of them can be removed to avoid confusion about whether there are two distinct scenarios being covered.

Similarly, the tests at lines 376–378 and 380–387 both assert that two undefined fields return false (the only difference is {} vs explicit undefined values, which behave the same way for optional fields). Consider collapsing those into a single test.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/reply-state.test.ts
Line: 367-396

Comment:
**Duplicate test cases**

The two tests at lines 367–374 and 389–396 are identical — both assert that `{ compactionCount: 0, memoryFlushCompactionCount: 0 }` returns `true`. One of them can be removed to avoid confusion about whether there are two distinct scenarios being covered.

Similarly, the tests at lines 376–378 and 380–387 both assert that two `undefined` fields return `false` (the only difference is `{}` vs explicit `undefined` values, which behave the same way for optional fields). Consider collapsing those into a single test.

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: 8b62e6b2b9

ℹ️ 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 src/auto-reply/reply/memory-flush.ts Outdated
Comment on lines +119 to +120
if (typeof entry.compactionCount !== "number") {
return 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.

P1 Badge Keep cycle-0 dedup for sessions missing compactionCount

Returning false whenever compactionCount is missing disables flush dedup for legacy/session-store entries that only have memoryFlushCompactionCount=0. In agent-runner-memory.ts, flush persistence still writes memoryFlushCompactionCount using a ?? 0 fallback, so these sessions will continue to look like "already flushed at cycle 0" data but this check now never recognizes it; the result is repeated memory flushes (duplicate MEMORY.md writes and extra model calls) on subsequent turns until a compaction event finally sets compactionCount.

Useful? React with 👍 / 👎.

Addresses Codex review on openclaw#66845:
- P1: Restore ?? 0 fallback for compactionCount when memoryFlushCompactionCount
  is set, preserving backward compat for legacy sessions
- P2: Remove duplicate test assertions

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@clawsweeper

clawsweeper Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Closing this as implemented after Codex automated review.

Current main already implements the final behavior in PR #66845: cycle-0 flush dedup treats missing compactionCount as 0 only when memoryFlushCompactionCount is present, and fresh sessions clear memoryFlushCompactionCount. The PR’s final runtime diff is behaviorally equivalent to main, after the PR discussion restored the compatibility fallback.

Best possible solution:

Close the PR as already implemented on main. Keep the shipped cycle-0 dedup semantics, where a persisted memoryFlushCompactionCount=0 with missing compactionCount is a valid already-flushed marker, and rely on the existing source/tests unless a new reproduction shows a distinct missed-flush path.

What I checked:

  • Current dedup implementation: hasAlreadyFlushedForCurrentCompaction resolves missing compactionCount to 0, then returns true only when memoryFlushCompactionCount is a number equal to that cycle. This preserves cycle-0 dedup and avoids repeated flushes. (src/auto-reply/reply/memory-flush.ts:108, d54d2d6b9b8a)
  • Regression coverage on main: The existing test suite explicitly asserts the compatibility case from the PR follow-up: { memoryFlushCompactionCount: 0 } with missing compactionCount is already flushed for cycle 0, while undefined memoryFlushCompactionCount returns false. (src/auto-reply/reply/reply-state.test.ts:351, d54d2d6b9b8a)
  • Flush metadata persistence path: runMemoryFlushIfNeeded derives memoryFlushCompactionCount from the active/store compaction count with a fallback to 0, and persists memoryFlushAt plus memoryFlushCompactionCount only after the flush run. That matches the dedup semantics on main. (src/auto-reply/reply/agent-runner-memory.ts:800, d54d2d6b9b8a)
  • Fresh-session initialization: New sessions set compactionCount to 0 and clear memoryFlushCompactionCount, so fresh sessions do not start in an already-flushed state. (src/auto-reply/reply/session.ts:770, d54d2d6b9b8a)
  • Release proof: Release v2026.4.24 contains the same dedup implementation and the same missing-compactionCount-as-0 test assertion. (src/auto-reply/reply/memory-flush.ts:108, cbcfdf62c729)
  • PR discussion and final head: The provided PR context shows the original commit changed behavior to require explicit compactionCount, then Codex review identified that as a P1 compatibility regression. The PR follow-up commit 413aa67 restored the ?? 0 fallback, leaving the runtime behavior equivalent to main and only adding redundant/narrower tests. (src/auto-reply/reply/memory-flush.ts:108, 413aa67a1082)

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: release v2026.4.24, commit cbcfdf62c729.

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.

1 participant