fix(memory-flush): allow first flush when compactionCount is 0#47174
fix(memory-flush): allow first flush when compactionCount is 0#47174MorikawaSouma wants to merge 2 commits into
Conversation
Previously, hasAlreadyFlushedForCurrentCompaction returned true when both compactionCount and memoryFlushCompactionCount were 0, which incorrectly blocked the first memory flush for sessions that had never been compacted. Now when compactionCount is 0 (never compacted), the function returns false to allow the first memory flush. This fixes the edge case where sessions with high token counts but no compaction history would never auto-flush. Fixes openclaw#47143
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5e3d48f1ed
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (compactionCount === 0) { | ||
| return false; |
There was a problem hiding this comment.
Preserve cycle gating when compaction count is zero
Returning false unconditionally when compactionCount is 0 removes the “already flushed this cycle” guard for the initial cycle, so sessions that have crossed the threshold can run memory flush on every turn before the first compaction. This is a regression because agent-runner-memory.ts persists memoryFlushCompactionCount as 0 when a flush run does not emit a compaction-end event, and this helper now ignores that persisted marker entirely for compactionCount=0, leading to repeated flush runs and duplicate memory writes.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR attempts to fix a bug where Key issues found:
Confidence Score: 2/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/auto-reply/reply/memory-flush.ts
Line: 234-236
Comment:
**Guard disabled for all `compactionCount === 0` sessions after first flush**
The early return unconditionally allows flushes whenever `compactionCount === 0`, but after the first flush completes, `memoryFlushCompactionCount` is stored as `0` (see `agent-runner-memory.ts` lines 523–526: `let memoryFlushCompactionCount = activeSessionEntry?.compactionCount ?? ... ?? 0`). On the very next evaluation, `compactionCount` is still `0`, so `hasAlreadyFlushedForCurrentCompaction` returns `false` again — allowing a second flush, and a third, and so on, until the session is finally compacted (incrementing `compactionCount` to ≥1).
The correct fix should distinguish between "never flushed" (`lastFlushAt === undefined`) and "already flushed at cycle 0" (`lastFlushAt === 0`):
```
// Before fix: 0 === 0 → true (incorrectly blocks first flush when memoryFlushCompactionCount
// was somehow 0 without a real flush occurring)
return typeof lastFlushAt === "number" && lastFlushAt === compactionCount;
```
A safer alternative that preserves the guard after the first flush completes:
```typescript
if (typeof lastFlushAt !== "number") {
// Never flushed yet — always allow.
return false;
}
return lastFlushAt === compactionCount;
```
This returns `false` when `lastFlushAt` is `undefined` (no flush recorded) regardless of `compactionCount`, and correctly returns `true` for `{compactionCount: 0, memoryFlushCompactionCount: 0}` after the first flush has been stored.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: pr-body.md
Line: 1-19
Comment:
**Stray `pr-body.md` file belongs to a different PR**
This file describes a fix for issue `#47139` (skill bundled-status display in the Control Panel), which is unrelated to this PR (`#47174`, the memory-flush `compactionCount === 0` bug). It appears to have been accidentally committed and should be removed from the repository.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/auto-reply/reply/reply-state.test.ts
Line: 389-406
Comment:
**Missing test for "already flushed at cycle 0" — the regression introduced by this fix**
The new test suite covers `{compactionCount: 0, memoryFlushCompactionCount: 0}` → `false`, but there is no test asserting what should happen *after* a flush has been persisted for `compactionCount = 0`. Because the implementation now unconditionally returns `false` when `compactionCount === 0`, the following case is not covered and would incorrectly return `false`:
```typescript
it("returns true when compactionCount is 0 and memoryFlushCompactionCount is 0 after a flush", () => {
// After runMemoryFlush completes for a never-compacted session, the store is
// updated with memoryFlushCompactionCount = 0. The guard must block a
// second flush within the same compaction cycle.
expect(
hasAlreadyFlushedForCurrentCompaction({
compactionCount: 0,
memoryFlushCompactionCount: 0, // flush already recorded
}),
).toBe(true); // currently fails with the new implementation
});
```
This directly relates to the regression described in the comment on `memory-flush.ts`: since there is no way to distinguish "never flushed" (`undefined`) from "already flushed at cycle 0" (`0`) with the current guard, flushes will repeat indefinitely for `compactionCount === 0` sessions.
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: 5e3d48f |
| if (compactionCount === 0) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Guard disabled for all compactionCount === 0 sessions after first flush
The early return unconditionally allows flushes whenever compactionCount === 0, but after the first flush completes, memoryFlushCompactionCount is stored as 0 (see agent-runner-memory.ts lines 523–526: let memoryFlushCompactionCount = activeSessionEntry?.compactionCount ?? ... ?? 0). On the very next evaluation, compactionCount is still 0, so hasAlreadyFlushedForCurrentCompaction returns false again — allowing a second flush, and a third, and so on, until the session is finally compacted (incrementing compactionCount to ≥1).
The correct fix should distinguish between "never flushed" (lastFlushAt === undefined) and "already flushed at cycle 0" (lastFlushAt === 0):
// Before fix: 0 === 0 → true (incorrectly blocks first flush when memoryFlushCompactionCount
// was somehow 0 without a real flush occurring)
return typeof lastFlushAt === "number" && lastFlushAt === compactionCount;
A safer alternative that preserves the guard after the first flush completes:
if (typeof lastFlushAt !== "number") {
// Never flushed yet — always allow.
return false;
}
return lastFlushAt === compactionCount;This returns false when lastFlushAt is undefined (no flush recorded) regardless of compactionCount, and correctly returns true for {compactionCount: 0, memoryFlushCompactionCount: 0} after the first flush has been stored.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/memory-flush.ts
Line: 234-236
Comment:
**Guard disabled for all `compactionCount === 0` sessions after first flush**
The early return unconditionally allows flushes whenever `compactionCount === 0`, but after the first flush completes, `memoryFlushCompactionCount` is stored as `0` (see `agent-runner-memory.ts` lines 523–526: `let memoryFlushCompactionCount = activeSessionEntry?.compactionCount ?? ... ?? 0`). On the very next evaluation, `compactionCount` is still `0`, so `hasAlreadyFlushedForCurrentCompaction` returns `false` again — allowing a second flush, and a third, and so on, until the session is finally compacted (incrementing `compactionCount` to ≥1).
The correct fix should distinguish between "never flushed" (`lastFlushAt === undefined`) and "already flushed at cycle 0" (`lastFlushAt === 0`):
```
// Before fix: 0 === 0 → true (incorrectly blocks first flush when memoryFlushCompactionCount
// was somehow 0 without a real flush occurring)
return typeof lastFlushAt === "number" && lastFlushAt === compactionCount;
```
A safer alternative that preserves the guard after the first flush completes:
```typescript
if (typeof lastFlushAt !== "number") {
// Never flushed yet — always allow.
return false;
}
return lastFlushAt === compactionCount;
```
This returns `false` when `lastFlushAt` is `undefined` (no flush recorded) regardless of `compactionCount`, and correctly returns `true` for `{compactionCount: 0, memoryFlushCompactionCount: 0}` after the first flush has been stored.
How can I resolve this? If you propose a fix, please make it concise.| ## Summary | ||
|
|
||
| Fixes #47139 | ||
|
|
||
| Previously, the Control Panel displayed workspace-installed skills as "bundled" when they shared names with bundled skills (e.g., summarize, nano-pdf, video-frames). This was because the bundled check used `bundledNames.has(entry.skill.name)` instead of checking the actual source path. | ||
|
|
||
| ### Root Cause | ||
| The `bundled` property in `buildSkillStatus` checked if the skill name existed in `bundledNames` set, rather than checking the actual `source` field of the skill entry. This caused workspace-installed copies of bundled skills to be incorrectly displayed as bundled. | ||
|
|
||
| ### Fix | ||
| Now the `bundled` property is determined solely by the skill's `source` field, which correctly reflects where the skill was loaded from. | ||
|
|
||
| ### Test Plan | ||
| 1. Install a skill from ClawHub that originally came bundled with OpenClaw (e.g., summarize, video-frames) | ||
| 2. Open Control Panel Web UI | ||
| 3. Check skills list | ||
| 4. Verify the skill shows as "workspace" instead of "bundled" | ||
|
|
||
| Also verify `openclaw skills list` and Control Panel show consistent source values. |
There was a problem hiding this comment.
Stray pr-body.md file belongs to a different PR
This file describes a fix for issue #47139 (skill bundled-status display in the Control Panel), which is unrelated to this PR (#47174, the memory-flush compactionCount === 0 bug). It appears to have been accidentally committed and should be removed from the repository.
Prompt To Fix With AI
This is a comment left during a code review.
Path: pr-body.md
Line: 1-19
Comment:
**Stray `pr-body.md` file belongs to a different PR**
This file describes a fix for issue `#47139` (skill bundled-status display in the Control Panel), which is unrelated to this PR (`#47174`, the memory-flush `compactionCount === 0` bug). It appears to have been accidentally committed and should be removed from the repository.
How can I resolve this? If you propose a fix, please make it concise.| it("returns false when compactionCount is 0 (never compacted)", () => { | ||
| // When compactionCount is 0, the session has never been compacted, | ||
| // so we should allow the first memory flush. | ||
| expect( | ||
| hasAlreadyFlushedForCurrentCompaction({ | ||
| compactionCount: 0, | ||
| memoryFlushCompactionCount: 0, | ||
| }), | ||
| ).toBe(true); | ||
| ).toBe(false); | ||
| }); | ||
|
|
||
| it("treats missing compactionCount as 0 and allows flush", () => { | ||
| expect( | ||
| hasAlreadyFlushedForCurrentCompaction({ | ||
| memoryFlushCompactionCount: 0, | ||
| }), | ||
| ).toBe(false); | ||
| }); |
There was a problem hiding this comment.
Missing test for "already flushed at cycle 0" — the regression introduced by this fix
The new test suite covers {compactionCount: 0, memoryFlushCompactionCount: 0} → false, but there is no test asserting what should happen after a flush has been persisted for compactionCount = 0. Because the implementation now unconditionally returns false when compactionCount === 0, the following case is not covered and would incorrectly return false:
it("returns true when compactionCount is 0 and memoryFlushCompactionCount is 0 after a flush", () => {
// After runMemoryFlush completes for a never-compacted session, the store is
// updated with memoryFlushCompactionCount = 0. The guard must block a
// second flush within the same compaction cycle.
expect(
hasAlreadyFlushedForCurrentCompaction({
compactionCount: 0,
memoryFlushCompactionCount: 0, // flush already recorded
}),
).toBe(true); // currently fails with the new implementation
});This directly relates to the regression described in the comment on memory-flush.ts: since there is no way to distinguish "never flushed" (undefined) from "already flushed at cycle 0" (0) with the current guard, flushes will repeat indefinitely for compactionCount === 0 sessions.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/reply-state.test.ts
Line: 389-406
Comment:
**Missing test for "already flushed at cycle 0" — the regression introduced by this fix**
The new test suite covers `{compactionCount: 0, memoryFlushCompactionCount: 0}` → `false`, but there is no test asserting what should happen *after* a flush has been persisted for `compactionCount = 0`. Because the implementation now unconditionally returns `false` when `compactionCount === 0`, the following case is not covered and would incorrectly return `false`:
```typescript
it("returns true when compactionCount is 0 and memoryFlushCompactionCount is 0 after a flush", () => {
// After runMemoryFlush completes for a never-compacted session, the store is
// updated with memoryFlushCompactionCount = 0. The guard must block a
// second flush within the same compaction cycle.
expect(
hasAlreadyFlushedForCurrentCompaction({
compactionCount: 0,
memoryFlushCompactionCount: 0, // flush already recorded
}),
).toBe(true); // currently fails with the new implementation
});
```
This directly relates to the regression described in the comment on `memory-flush.ts`: since there is no way to distinguish "never flushed" (`undefined`) from "already flushed at cycle 0" (`0`) with the current guard, flushes will repeat indefinitely for `compactionCount === 0` sessions.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e4da6a5188
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (compactionCount === 0) { | ||
| return false; |
There was a problem hiding this comment.
Keep flush-cycle guard when compaction count is zero
Returning false for every compactionCount === 0 drops the duplicate-run guard for the entire pre-compaction cycle: after the first flush, runMemoryFlushTurn persists memoryFlushCompactionCount to the current count (which is still 0 when no compaction-end event is emitted), but this helper now ignores that marker and allows another flush on every subsequent turn that stays above threshold. That causes repeated memory-flush runs and duplicate memory writes until compaction finally increments.
Useful? React with 👍 / 👎.
|
Closing this as not reproducible on current Close PR #47174. Current What I checked:
Review notes: reviewed against 0bef73d151bd. |
Summary
Fixes #47143
Root Cause
hasAlreadyFlushedForCurrentCompactionreturnedtruewhen bothcompactionCountandmemoryFlushCompactionCountwere0. This incorrectly blocked the first memory flush for sessions that had never been compacted.Fix
When
compactionCountis0(never compacted), the function now returnsfalseto allow the first memory flush.Impact
Test Plan
hasAlreadyFlushedForCurrentCompactiontests to reflect new behaviorreturns false when compactionCount is 0 (never compacted)verifies the fix