Skip to content

fix(lcm-recent): preserve historical rollups during pending rebuild#29

Merged
100yenadmin merged 2 commits into
mainfrom
fix/lcm-recent-pending-rebuild-historical
May 4, 2026
Merged

fix(lcm-recent): preserve historical rollups during pending rebuild#29
100yenadmin merged 2 commits into
mainfrom
fix/lcm-recent-pending-rebuild-historical

Conversation

@100yenadmin

@100yenadmin 100yenadmin commented May 4, 2026

Copy link
Copy Markdown
Member

Summary

Fixes lcm_recent serving fallback instead of stored historical rollups whenever pending_rebuild=1.

The read-path freshness gate was too broad: any explicit day request was treated as unsafe while rebuild was pending, even if last_message_at was outside the requested historical date/window. This made closed dates like date:2026-05-02 bypass ready stored rollups after current-day activity dirtied the conversation.

Policy

  • Current day remains fresh-source fallback.
  • Pending rebuild touching the requested window remains fallback.
  • Pending rebuild with unknown freshness bounds remains conservative fallback.
  • Historical day/week/month windows serve stored rollups when pending activity is outside the requested window.
  • Bounded raw fallback excludes system/tool messages and obvious OpenClaw internal context markers.

Bug report

Issues are disabled on this repo, so filing the bug as this PR instead.

Repro

  1. Build rollups for prior dates.
  2. Add new current-day activity so lcm_rollup_state.pending_rebuild=1 and last_message_at points to today/current window.
  3. Call lcm_recent({ period: "date:<prior closed date>" }).

Before: response was fallback with “Rollup rebuild is pending…” even though a ready stored day rollup existed.

After: response serves the ready stored historical rollup.

Verification

npm test -- --run test/rollup-store-builder.test.ts test/lcm-rebuild-rollups.test.ts test/lcm-integration.test.ts test/lcm-command.test.ts
npm test -- --run
npm run build

Local results:

  • 4 targeted test files passed: 181 tests
  • full test suite passed: 49 files, 935 tests
  • build passed

Summary by CodeRabbit

  • Refactor

    • Added consistent message sanitization to exclude internal/tool/system markers and restrict fallback messages to visible roles
    • Improved rollup freshness parsing and pending-rebuild handling, including an "unknown" pending-rebuild state and refined fallback eligibility across days and aggregates
  • Tests

    • Added bounded-fallback tests validating sanitized outputs and match counts
    • Expanded pending-rebuild and weekly-aggregate tests to cover regression and probe-edge cases

Copilot AI review requested due to automatic review settings May 4, 2026 12:24
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Sanitizes “message:” fallback evidence to only include user/assistant roles and exclude internal markers; applies the predicate to fetch, count, and probe SQL. Refactors pending-rollup freshness handling by parsing/validating last_message_at, introducing pendingRebuildUnknown, and adjusting when stored rollups are bypassed or reused.

Changes

Fallback Filtering and Pending Rollup Refactor

Layer / File(s) Summary
Sanitization helpers
src/tools/lcm-recent-tool.ts
Add internal-context marker list, escapeSqlLiteral, and buildSanitizedMessagePredicate(messageAlias) to centralize SQL filtering logic.
Fallback fetch / probe
src/tools/lcm-recent-tool.ts
Apply buildSanitizedMessagePredicate("m") to the messages fallback SQL, to availableCount probe, and to EXISTS presence probe so fetch/count/probe share the same sanitized criteria.
Pending-rollup freshness parsing
src/tools/lcm-recent-tool.ts
Parse/validate last_message_at only when pending_rebuild === 1; introduce pendingRebuildUnknown when last_message_at is missing/invalid and compute pendingRebuildTouchesWindow accordingly.
Stored vs live rollup gating
src/tools/lcm-recent-tool.ts
Adjust conditions that bypass stored resolved rollups to account for pendingRebuildUnknown and tighten cross-conversation aggregate reuse logic; preserve day-specific bypass behavior using pendingRebuildTouchesWindow.
Day-key live-fallback population
src/tools/lcm-recent-tool.ts
When resolution.kind === "day" and hasPendingRebuild, replace prior pendingDayKey missing branch with explicit pendingRebuildUnknown handling, changing which day keys are added for live fallback population.
Tests: bounded fallback sanitization
test/rollup-store-builder.test.ts
New Vitest case asserts tool/system/internal raw messages are excluded from bounded fallback output and totalMatches counts only visible assistant message.
Tests: pending-rebuild day/weekly regressions
test/rollup-store-builder.test.ts
Add/adjust tests: (1) pending-later-day uses stored ready daily rollup (no fallback), (2) fallback probing ignores tool/system/internal raw rows so stored rollups aren’t shadowed, (3) weekly aggregate behavior when pending rebuild is inside vs outside the requested window.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: fixing lcm-recent to preserve historical rollups when rebuild is pending, which is the core issue addressed throughout both modified files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/lcm-recent-pending-rebuild-historical

Review rate limit: 0/5 reviews remaining, refill in 59 minutes and 36 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR narrows the lcm_recent “pending rebuild” freshness gate so that historical day/week/month requests can still serve ready stored rollups when the pending activity (last_message_at) is outside the requested window, while keeping conservative fallback behavior for current-day and unknown-bounds cases.

Changes:

  • Refines pending-rebuild gating to only bypass stored rollups when the pending activity intersects the requested window (or when bounds are unknown).
  • Hardens bounded raw-message fallback to exclude tool/system roles and internal context markers from being surfaced.
  • Updates/extends tests to cover historical rollup preservation during pending rebuilds and sanitized fallback behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/tools/lcm-recent-tool.ts Tightens pending-rebuild window detection and filters raw fallback messages to avoid leaking tool/system/internal context.
test/rollup-store-builder.test.ts Adds coverage for sanitized fallback and verifies stored historical rollups remain usable when pending rebuild is for later/out-of-window activity.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/tools/lcm-recent-tool.ts Outdated
Comment on lines +1155 to +1158
AND m.role IN ('user', 'assistant')
AND instr(m.content, '<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>') = 0
AND instr(m.content, '<<<END_OPENCLAW_INTERNAL_CONTEXT>>>') = 0
AND instr(m.content, '[lossless-claw] missing tool result') = 0
Comment on lines 1500 to +1504
resolution.periodKey == null || resolution.periodKey !== currentDayKey;
const hasPendingRebuild = rollupState?.pending_rebuild === 1;
const pendingRebuildUnknown = hasPendingRebuild && lastPendingMessageAt == null;
const pendingRebuildTouchesWindow =
hasPendingRebuild && (resolution.kind === "day" || pendingDayKey != null);
hasPendingRebuild && (pendingRebuildUnknown || pendingDayKey != null);

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/tools/lcm-recent-tool.ts`:
- Around line 1502-1506: The code currently treats an unparseable
last_message_at (resulting in an invalid Date in lastPendingMessageAt) as if it
were present, making pendingRebuildUnknown false; update the logic to treat
invalid Dates as unknown by checking for NaN/getTime() before using
lastPendingMessageAt. For example, normalize lastPendingMessageAt or change the
pendingRebuildUnknown computation to use hasPendingRebuild &&
(lastPendingMessageAt == null || isNaN(lastPendingMessageAt.getTime())) so
pendingRebuildTouchesWindow and canUseStoredResolvedRollup behave correctly when
last_message_at is unparseable.
- Around line 1585-1589: The branch that returns stored rollups for
resolution.kind when resolution.window exists incorrectly allows using stored
weekly/monthly aggregates even if the pending rebuild overlaps that window;
update the condition that now reads "resolution.kind && !resolution.window &&
!pendingRebuildUnknown" to also exclude cases where the pending rebuild touches
the resolution.window (e.g., check pendingDayKey or the pending rebuild range
against resolution.window). Concretely, add an overlap check (use an existing
helper like pendingRebuildContains or add a small isWithinWindow(pendingDayKey,
resolution.window) check) and change the condition to require no overlap (e.g.,
&& !pendingRebuildUnknown && !pendingRebuildOverlapsWindow) so touched aggregate
windows fall through to fallback instead of returning stored rollups.
- Around line 1155-1158: The count/coverage subqueries (notably availableCount
and hasFallbackSourceItemsInRange()) are still counting unsanitized rows
(tool/system/internal and messages containing the internal markers) while the
fetch query filters them out, causing totalMatches inflation; update those
downstream message-counting subqueries to apply the exact same sanitized
predicate used in the fetch (i.e., AND m.role IN ('user','assistant') AND
instr(m.content,'<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>') = 0 AND
instr(m.content,'<<<END_OPENCLAW_INTERNAL_CONTEXT>>>') = 0 AND
instr(m.content,'[lossless-claw] missing tool result') = 0) so availableCount,
hasFallbackSourceItemsInRange(), and any other message-counting expressions use
consistent filtering with the fetch path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9b0d4110-ee02-41f4-9377-99a798aff68f

📥 Commits

Reviewing files that changed from the base of the PR and between f34f12b and 74e8b97.

📒 Files selected for processing (2)
  • src/tools/lcm-recent-tool.ts
  • test/rollup-store-builder.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Agent
  • GitHub Check: test
  • GitHub Check: test

Comment thread src/tools/lcm-recent-tool.ts Outdated
Comment thread src/tools/lcm-recent-tool.ts
Comment thread src/tools/lcm-recent-tool.ts
Eva added 2 commits May 4, 2026 23:28
- C1 (P0) Sanitized message predicate now consistently applied in all 3
  query sites (availableCount, hasFallbackSourceItemsInRange, fetch).
  Extracted SANITIZED_FALLBACK_INTERNAL_MARKERS + buildSanitizedMessagePredicate
  so role + internal-marker filters can never drift between fetch and
  count/coverage paths.
- C2 (P0) Added pendingRebuildTouchesWindow gate to weekly/monthly stored
  rollup branch. Day branch already covered by per-day usableKeys filter.
- M1 (P1) Treat unparseable last_message_at as unknown bounds (Number.isNaN
  check at the source), so corrupt rollup state cannot silently serve stale
  data via Invalid Date that compares != null.
- M2 (P2) Centralized internal-marker filter list in
  SANITIZED_FALLBACK_INTERNAL_MARKERS; new markers go in one place.
- 3 regression tests:
  * "ignores tool/system/internal raw rows when probing fallback presence"
    (C1: stored rollup wins over harness-only-fallback signal)
  * "bypasses stored weekly rollup when pending rebuild touches the window"
    (C2: weekly aggregate falls through to fallback when pendingDayKey lands
    inside it)
  * "treats unparseable last_message_at as unknown pending bounds"
    (M1: corrupt timestamp -> unknown-bounds degraded reason, stale rollup
    bypassed)
@100yenadmin 100yenadmin force-pushed the fix/lcm-recent-pending-rebuild-historical branch from 74e8b97 to 8c56c0a Compare May 4, 2026 16:32
@100yenadmin 100yenadmin merged commit cf28b6e into main May 4, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants