Skip to content

fix: wire SQLite session history compaction - #560

Merged
phodal merged 3 commits into
phodal:mainfrom
cloudyli:codex/pr3-session-compaction
May 23, 2026
Merged

fix: wire SQLite session history compaction#560
phodal merged 3 commits into
phodal:mainfrom
cloudyli:codex/pr3-session-compaction

Conversation

@cloudyli

@cloudyli cloudyli commented May 23, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add a SQLite maintenance command for compacting Routa ACP session history in local dev/runtime databases.
  • Reuse and harden session history chunk compaction so ACP payloads using update.content.text compact correctly.
  • Protect active sessions by lease, recent update window, or explicit --active-session during maintenance.
  • Document safe dry-run/apply/checkpoint/vacuum usage for local SQLite operation.

Scope

This PR is intentionally limited to local SQLite session-history maintenance. It does not implement metadata protection, retention policy scheduling, Postgres migration, workflow gates, or QuantDinger-specific behavior.

Validation

Passed:

  • npm test -- --run src/core/storage/__tests__/history-compactor.test.ts scripts/__tests__/compact-session-history.test.ts
  • node --import tsx -e import('./scripts/maintenance/compact-session-history.ts').then(m=>console.log(Object.keys(m)))
  • npx tsc --noEmit --pretty false
  • npx eslint src/core/storage/history-compactor.ts src/core/storage/__tests__/history-compactor.test.ts scripts/maintenance/compact-session-history.ts scripts/__tests__/compact-session-history.test.ts
  • git diff --check
  • cargo fmt --check
  • npm run test:run:fast
  • cargo build -p entrix
  • ./target/debug/entrix.exe run --dry-run

Local integration validation with PR1+PR2+PR3 stacked:

  • npm test -- --run src/core/kanban/__tests__/agent-trigger.test.ts src/core/kanban/__tests__/completion-fallback-artifact.test.ts src/core/mcp/__tests__/mcp-tool-executor.test.ts src/core/mcp/__tests__/mcp-tool-manager.test.ts src/core/storage/__tests__/history-compactor.test.ts scripts/__tests__/compact-session-history.test.ts
  • npx tsc --noEmit --pretty false
  • cargo test -p routa-server --test rust_api_mcp_routes api_mcp_kanban_profile_filters_tools_list -- --nocapture
  • npm run db:sqlite:compact-sessions -- --db C:\Project\Routa\routa.db --dry-run --json
  • GET http://127.0.0.1:3890/api/health -> 200
  • GET http://127.0.0.1:3890/workspace/quantdinger/kanban -> 200

Attempted but not clean in this Windows worktree:

  • ./target/debug/entrix.exe run --tier fast

Observed failures appear unrelated to this PR scope: Windows/path checker errors, npm mirror audit endpoint not implemented, existing desktop clippy dead-code in apps/desktop/src-tauri/src/tray.rs, and whole-repo lint issues outside the touched files. Targeted tests/lint/typecheck for this PR pass.

Summary by CodeRabbit

  • New Features

    • Added a CLI maintenance tool to compact local SQLite session history with dry-run and apply modes, JSON or human output, optional checkpoint and VACUUM, and protections for recent/active sessions and unexpired leases.
    • Merges consecutive chunked agent messages into single messages and rewrites storage to reduce space.
  • Documentation

    • Added Chinese operational guide describing maintenance steps, modes, protections, and recommendations.
  • Tests

    • Added tests covering compaction, merging, protections, and dry-run vs apply behavior.

Review Change Stack

@cloudyli
cloudyli requested a review from phodal as a code owner May 23, 2026 11:07
@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds CLI-driven SQLite session history compaction: core compaction helpers, a TypeScript maintenance CLI (dry-run / apply / checkpoint / vacuum), unit and integration tests, an npm script, and Chinese operational documentation.

Changes

Session History Maintenance Feature

Layer / File(s) Summary
Core session history compaction helpers and integration
src/core/storage/history-compactor.ts
Adds SessionHistoryNotification and SessionHistoryCompactionResult; implements getSessionHistoryChunkText, isSessionHistoryChunk, and compactSessionHistoryNotifications(); integrates the helper into session-compression flow.
Compaction helper tests
src/core/storage/__tests__/history-compactor.test.ts
Adds tests for chunk text extraction, merging consecutive agent_message_chunk notifications, and legacy-chunk normalization.
CLI maintenance script implementation
scripts/maintenance/compact-session-history.ts
New CLI with runSessionHistoryMaintenance and main: parses options, identifies protected sessions, groups consecutive agent_message_chunk rows, builds merged agent_message payloads via compaction helpers, plans/applies DB updates/deletes in --apply mode, compacts acp_sessions.message_history, and optionally runs PRAGMA wal_checkpoint(TRUNCATE) and VACUUM. Supports JSON output and safety defaults.
CLI integration tests
scripts/__tests__/compact-session-history.test.ts
Creates temporary BetterSqlite3 fixtures with old and active sessions; verifies dry-run reporting (no row changes) and apply-mode compaction/deletion and JSON updates.
Feature wiring and operational documentation
package.json, docs/operational/sqlite-session-history-maintenance.zh-CN.md
Adds db:sqlite:compact-sessions npm script and Chinese operational documentation covering disk-usage sources, CLI options (--dry-run, --apply, --checkpoint, --vacuum), protection windows, merge rules, and VACUUM guidance.

Sequence Diagram

sequenceDiagram
  participant CLI as compact-session-history.ts
  participant Compactor as compactSessionHistoryNotifications
  participant DB as SQLite
  CLI->>DB: Query candidate session_messages and acp_sessions
  DB-->>CLI: Return rows and JSON history
  CLI->>Compactor: Request merge of consecutive chunk notifications
  Compactor-->>CLI: Return merged agent_message payloads and stats
  CLI->>DB: Update first chunk to agent_message and delete merged chunk rows (apply)
  CLI->>DB: Run WAL checkpoint or VACUUM (optional)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • phodal

Poem

A rabbit cleans the database bright 🐰
Chunks stitched into messages, tidy and light ✨
Old rows compacted, the active ones spared,
WAL and VACUUM tidy when service is prepared 🧹
The rabbit hops home — database airy and right 🥕

🚥 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
Title check ✅ Passed The title 'fix: wire SQLite session history compaction' directly describes the main change: wiring up the SQLite session history compaction functionality across the codebase.
Description check ✅ Passed The description covers all required template sections (Summary with what/why, Validation with specific test commands) and provides comprehensive additional context on scope and integration testing.
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 unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed: dependency version conflict. Check your lock file or package.json.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 50a3fda9d1

ℹ️ 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 on lines +173 to +180
return {
...first,
update: {
...(typeof first.update === "object" && first.update ? first.update : {}),
sessionUpdate: "agent_message",
content: { type: "text", text },
mergedFrom: group.length,
},

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 Normalize legacy fields when merging chunk payloads

buildMergedPayload only writes update.sessionUpdate/content and keeps the first row’s top-level fields unchanged. For legacy chunk rows (e.g., payloads shaped like { type: "agent_message_chunk", text: ... }), this leaves type as agent_message_chunk and text as only the first chunk after the other chunk rows are deleted, so consumers that still read legacy keys (or helpers that prioritize top-level text) will see truncated/mislabeled messages. This commit already introduces legacy normalization in compactSessionHistoryNotifications, so the maintenance merge path should apply the same normalization for session_messages rows.

Useful? React with 👍 / 👎.

@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: 1

🧹 Nitpick comments (2)
scripts/maintenance/compact-session-history.ts (1)

170-182: ⚡ Quick win

Use the shared compaction helper for session_messages payload merge too.

buildMergedPayload() duplicates compaction logic and can drift from compactSessionHistoryNotifications behavior over time. Reusing the helper here keeps both maintenance paths consistent.

♻️ Suggested refactor
 function buildMergedPayload(group: MessageRow[]): Record<string, unknown> {
-  const first = parsePayload(group[0].payload);
-  const text = group.map((row) => getSessionHistoryChunkText(parsePayload(row.payload))).join("");
-  return {
-    ...first,
-    update: {
-      ...(typeof first.update === "object" && first.update ? first.update : {}),
-      sessionUpdate: "agent_message",
-      content: { type: "text", text },
-      mergedFrom: group.length,
-    },
-  };
+  const normalizedHistory = group.map((row) => ({
+    ...(parsePayload(row.payload) as SessionHistoryNotification),
+    sessionId: row.session_id,
+  }));
+  const compacted = compactSessionHistoryNotifications(normalizedHistory);
+  return (compacted.history[0] ?? normalizedHistory[0]) as Record<string, unknown>;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/maintenance/compact-session-history.ts` around lines 170 - 182,
buildMergedPayload duplicates the session_messages compaction logic; replace its
manual payload construction with the shared compaction helper used by
compactSessionHistoryNotifications to keep behavior consistent. In practice,
modify buildMergedPayload(group) to parse the first.payload, compute the merged
text (or pass group) into the shared helper (the same function used by
compactSessionHistoryNotifications), and return the helper's merged payload
shape (preserving sessionUpdate, content.type/text, and mergedFrom count)
instead of manually assembling update/content. Ensure you call the helper with
the same inputs and map its result into the expected Record<string, unknown>
return.
scripts/__tests__/compact-session-history.test.ts (1)

91-94: ⚡ Quick win

Always close test SQLite handles with try/finally.

If an assertion throws before sqlite.close(), temp cleanup can become flaky (especially on file-locking platforms).

✅ Suggested change
-    const sqlite = new BetterSqlite3(dbPath);
-    expect(sqlite.prepare("SELECT COUNT(*) AS count FROM session_messages").get()).toEqual({ count: 4 });
-    sqlite.close();
+    const sqlite = new BetterSqlite3(dbPath);
+    try {
+      expect(sqlite.prepare("SELECT COUNT(*) AS count FROM session_messages").get()).toEqual({ count: 4 });
+    } finally {
+      sqlite.close();
+    }
-    const sqlite = new BetterSqlite3(dbPath);
-    expect(sqlite.prepare("SELECT COUNT(*) AS count FROM session_messages").get() as CountRow).toEqual({ count: 3 });
-    const oldPayloadRow = sqlite.prepare("SELECT payload FROM session_messages WHERE id = ?").get("m1") as PayloadRow;
-    const oldPayload = JSON.parse(oldPayloadRow.payload);
-    expect(oldPayload.update.sessionUpdate).toBe("agent_message");
-    expect(oldPayload.update.content.text).toBe("AB");
-
-    const activePayloadRow = sqlite.prepare("SELECT payload FROM session_messages WHERE id = ?").get("m3") as PayloadRow;
-    const activePayload = JSON.parse(activePayloadRow.payload);
-    expect(activePayload.update.sessionUpdate).toBe("agent_message_chunk");
-
-    const oldSession = sqlite.prepare("SELECT message_history FROM acp_sessions WHERE id = ?").get("old-session") as SessionHistoryRow;
-    expect(JSON.parse(oldSession.message_history)).toHaveLength(2);
-    const activeSession = sqlite.prepare("SELECT message_history FROM acp_sessions WHERE id = ?").get("active-session") as SessionHistoryRow;
-    expect(JSON.parse(activeSession.message_history)).toHaveLength(2);
-    sqlite.close();
+    const sqlite = new BetterSqlite3(dbPath);
+    try {
+      expect(sqlite.prepare("SELECT COUNT(*) AS count FROM session_messages").get() as CountRow).toEqual({ count: 3 });
+      const oldPayloadRow = sqlite.prepare("SELECT payload FROM session_messages WHERE id = ?").get("m1") as PayloadRow;
+      const oldPayload = JSON.parse(oldPayloadRow.payload);
+      expect(oldPayload.update.sessionUpdate).toBe("agent_message");
+      expect(oldPayload.update.content.text).toBe("AB");
+
+      const activePayloadRow = sqlite.prepare("SELECT payload FROM session_messages WHERE id = ?").get("m3") as PayloadRow;
+      const activePayload = JSON.parse(activePayloadRow.payload);
+      expect(activePayload.update.sessionUpdate).toBe("agent_message_chunk");
+
+      const oldSession = sqlite.prepare("SELECT message_history FROM acp_sessions WHERE id = ?").get("old-session") as SessionHistoryRow;
+      expect(JSON.parse(oldSession.message_history)).toHaveLength(2);
+      const activeSession = sqlite.prepare("SELECT message_history FROM acp_sessions WHERE id = ?").get("active-session") as SessionHistoryRow;
+      expect(JSON.parse(activeSession.message_history)).toHaveLength(2);
+    } finally {
+      sqlite.close();
+    }

Also applies to: 110-125

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/__tests__/compact-session-history.test.ts` around lines 91 - 94, The
test opens a BetterSqlite3 handle with `const sqlite = new
BetterSqlite3(dbPath)` but calls `sqlite.close()` directly, which can leak the
DB handle if an assertion throws; wrap the creation and all uses of `sqlite`
(the BetterSqlite3 instance) in a try/finally so that `sqlite.close()` is always
executed in the finally block, and apply the same change to the other occurrence
around lines 110-125 where a `sqlite` handle is created and closed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/storage/history-compactor.ts`:
- Around line 244-247: The compaction code takes only the first element of
compactSessionHistoryNotifications(...) into mergedPayload and then deletes the
remaining group rows, which discards additional compacted entries when
compaction returns multiple outputs; update the logic around
compactSessionHistoryNotifications (the mergedPayload creation and subsequent
delete/replace steps) to handle all entries returned by
compactSessionHistoryNotifications: iterate the resulting .history array,
write/replace each compacted entry into the DB (or map them back to group rows)
instead of using only .history[0], and adjust deletion/insertion so no compacted
entries are lost; apply the same multi-entry handling fix to the analogous block
around lines 266-271.

---

Nitpick comments:
In `@scripts/__tests__/compact-session-history.test.ts`:
- Around line 91-94: The test opens a BetterSqlite3 handle with `const sqlite =
new BetterSqlite3(dbPath)` but calls `sqlite.close()` directly, which can leak
the DB handle if an assertion throws; wrap the creation and all uses of `sqlite`
(the BetterSqlite3 instance) in a try/finally so that `sqlite.close()` is always
executed in the finally block, and apply the same change to the other occurrence
around lines 110-125 where a `sqlite` handle is created and closed.

In `@scripts/maintenance/compact-session-history.ts`:
- Around line 170-182: buildMergedPayload duplicates the session_messages
compaction logic; replace its manual payload construction with the shared
compaction helper used by compactSessionHistoryNotifications to keep behavior
consistent. In practice, modify buildMergedPayload(group) to parse the
first.payload, compute the merged text (or pass group) into the shared helper
(the same function used by compactSessionHistoryNotifications), and return the
helper's merged payload shape (preserving sessionUpdate, content.type/text, and
mergedFrom count) instead of manually assembling update/content. Ensure you call
the helper with the same inputs and map its result into the expected
Record<string, unknown> return.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 17926ae9-8bd0-43d3-b402-a9ac0b22e9bf

📥 Commits

Reviewing files that changed from the base of the PR and between d48e2b0 and 50a3fda.

📒 Files selected for processing (6)
  • docs/operational/sqlite-session-history-maintenance.zh-CN.md
  • package.json
  • scripts/__tests__/compact-session-history.test.ts
  • scripts/maintenance/compact-session-history.ts
  • src/core/storage/__tests__/history-compactor.test.ts
  • src/core/storage/history-compactor.ts

Comment thread src/core/storage/history-compactor.ts Outdated
Co-authored-by: Codex (GPT 5.5) <codex@openai.com>
@cloudyli
cloudyli force-pushed the codex/pr3-session-compaction branch from 50a3fda to 24298cc Compare May 23, 2026 11:29

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 24298ccd84

ℹ️ 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 on lines +186 to +193
return {
...first,
update: {
...(typeof first.update === "object" && first.update ? first.update : {}),
sessionUpdate: "agent_message",
content: { type: "text", text },
mergedFrom: group.length,
},

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 Normalize legacy chunk fields in merged SQLite payloads

When session_messages.payload uses the legacy top-level shape (for example { "type": "agent_message_chunk", "text": "..." }), buildMergedPayload only updates update.sessionUpdate/content and leaves the copied top-level fields from the first row unchanged. After compaction, the row is marked as event_type='agent_message' but its top-level type/text can still indicate a chunk and only contain the first fragment, so readers that still prefer legacy fields will see truncated or mislabeled messages.

Useful? React with 👍 / 👎.

@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: 1

♻️ Duplicate comments (1)
src/core/storage/history-compactor.ts (1)

243-247: ⚠️ Potential issue | 🟠 Major

Handle all compacted outputs before deleting the trailing rows.

compactSessionHistoryNotifications(...) can still return more than one entry here when the stored payloads have mixed or missing sessionId values. This path keeps only .history[0] and then deletes the rest of the group, so later compacted entries are lost. Normalize the notifications with the row-level sessionId before compacting, or map every returned history item back to DB rows instead of assuming a single output.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/storage/history-compactor.ts` around lines 243 - 247,
compactSessionHistoryNotifications(...) can return multiple history entries but
the current code only uses .history[0] (mergedPayload) and deletes the rest of
the group, losing data; fix this in history-compactor.ts by first normalizing
each input payload with the row-level sessionId (use each group's chunk.payload
and the chunk.row/sessionId) before calling compactSessionHistoryNotifications,
then iterate over every returned history item from
compactSessionHistoryNotifications(...).history and map each item back to its
corresponding DB row(s) (rather than assuming a single output) so you
update/insert the correct rows and only delete trailing rows that were fully
merged; reference compactSessionHistoryNotifications,
SessionHistoryNotification, group, and mergedPayload to locate the change.
🧹 Nitpick comments (1)
scripts/__tests__/compact-session-history.test.ts (1)

96-125: ⚡ Quick win

Add one legacy-payload apply-mode assertion here.

These tests only cover update.sessionUpdate-style rows, so they would miss a regression where the CLI leaves top-level legacy chunk markers (type/eventType) behind after merge. A fixture with legacy chunk payloads plus an assertion that the merged row comes back as agent_message would lock down the path changed in buildMergedPayload().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/__tests__/compact-session-history.test.ts` around lines 96 - 125,
Update the "applies compaction and skips active sessions" test to add a
legacy-payload assertion: after running runSessionHistoryMaintenance({ mode:
"apply", ... }) query the merged message payload (same approach used for m1/m3)
for the legacy chunk case, JSON.parse it and assert that the merged payload uses
the normalized shape (e.g. update.sessionUpdate === "agent_message" and the
expected content text like "AB"); this ensures the code path in
buildMergedPayload() that converts top-level legacy chunk markers
(type/eventType) into the new update.sessionUpdate format is exercised and
prevents regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/maintenance/compact-session-history.ts`:
- Around line 183-194: The current buildMergedPayload function hand-rolls a
partial payload merge which preserves legacy top-level chunk markers
(type/eventType/text/content) and can cause merged rows to be re-detected as
chunks; replace the manual merge with a call to
compactSessionHistoryNotifications(...) to construct the merged payload:
normalize sessionId from group[0].session_id (or row.session_id), assemble the
merged text via getSessionHistoryChunkText(parsePayload(...)) as before, and
pass that into compactSessionHistoryNotifications so it produces a canonical
payload that sets sessionUpdate, content, mergedFrom, and removes legacy
top-level markers; update references in buildMergedPayload to use
compactSessionHistoryNotifications and remove the manual update merge logic so
isSessionHistoryChunk() no longer misclassifies merged rows.

---

Duplicate comments:
In `@src/core/storage/history-compactor.ts`:
- Around line 243-247: compactSessionHistoryNotifications(...) can return
multiple history entries but the current code only uses .history[0]
(mergedPayload) and deletes the rest of the group, losing data; fix this in
history-compactor.ts by first normalizing each input payload with the row-level
sessionId (use each group's chunk.payload and the chunk.row/sessionId) before
calling compactSessionHistoryNotifications, then iterate over every returned
history item from compactSessionHistoryNotifications(...).history and map each
item back to its corresponding DB row(s) (rather than assuming a single output)
so you update/insert the correct rows and only delete trailing rows that were
fully merged; reference compactSessionHistoryNotifications,
SessionHistoryNotification, group, and mergedPayload to locate the change.

---

Nitpick comments:
In `@scripts/__tests__/compact-session-history.test.ts`:
- Around line 96-125: Update the "applies compaction and skips active sessions"
test to add a legacy-payload assertion: after running
runSessionHistoryMaintenance({ mode: "apply", ... }) query the merged message
payload (same approach used for m1/m3) for the legacy chunk case, JSON.parse it
and assert that the merged payload uses the normalized shape (e.g.
update.sessionUpdate === "agent_message" and the expected content text like
"AB"); this ensures the code path in buildMergedPayload() that converts
top-level legacy chunk markers (type/eventType) into the new
update.sessionUpdate format is exercised and prevents regressions.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: b126c1fa-02be-426e-b78c-990fa9800d1b

📥 Commits

Reviewing files that changed from the base of the PR and between 50a3fda and 24298cc.

📒 Files selected for processing (6)
  • docs/operational/sqlite-session-history-maintenance.zh-CN.md
  • package.json
  • scripts/__tests__/compact-session-history.test.ts
  • scripts/maintenance/compact-session-history.ts
  • src/core/storage/__tests__/history-compactor.test.ts
  • src/core/storage/history-compactor.ts
✅ Files skipped from review due to trivial changes (2)
  • docs/operational/sqlite-session-history-maintenance.zh-CN.md
  • package.json

Comment thread scripts/maintenance/compact-session-history.ts Outdated
Co-authored-by: Codex (GPT 5.5) <codex@openai.com>

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/maintenance/compact-session-history.ts`:
- Around line 183-191: The code currently throws if
compactSessionHistoryNotifications(...) (used with parsePayload on each row)
doesn't produce exactly one merged entry (compacted.compactedCount !== 1);
change this so invalid groups are skipped instead of aborting: during planning
precompute the merged result for each group by calling
compactSessionHistoryNotifications and treat groups that don't yield a single
merged history (i.e., where merged is falsy or compacted.compactedCount !== 1)
as invalid and drop them from both the summary.sessionMessages.* and the apply
list, mirroring HistoryCompactor.compressOldSessions() behavior so only
validated groups are executed in apply.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: d63197d7-3fb9-4238-8c33-96a7802d767e

📥 Commits

Reviewing files that changed from the base of the PR and between 24298cc and 80ae27e.

📒 Files selected for processing (3)
  • scripts/__tests__/compact-session-history.test.ts
  • scripts/maintenance/compact-session-history.ts
  • src/core/storage/history-compactor.ts

Comment thread scripts/maintenance/compact-session-history.ts

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 80ae27e9fc

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

applyMessageGroups(groups);
applySessionJson(sessionJsonUpdates);
if (options.checkpoint) {
sqlite.pragma("wal_checkpoint(TRUNCATE)");

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 Check WAL checkpoint result before reporting success

When --checkpoint is used, this call ignores the result of PRAGMA wal_checkpoint(TRUNCATE), but SQLite can return a busy status (first result column = 1) without throwing if readers/writers are still active. In that case the command currently reports checkpoint as applied even though truncation did not complete, which gives operators a false success signal and can leave the WAL file unreclaimed after maintenance.

Useful? React with 👍 / 👎.

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/maintenance/compact-session-history.ts`:
- Around line 187-195: The buildMergedPayload function currently calls
parsePayload on every row inside a map, which lets a single malformed
session_messages.payload throw and abort the whole maintenance run; update
buildMergedPayload to safely parse each row before calling
compactSessionHistoryNotifications: iterate over group and for each row call
parsePayload inside a try/catch (or a safeParse helper) and if any parse fails,
return null to skip that whole group; only build the array passed to
compactSessionHistoryNotifications from successfully parsed
SessionHistoryNotification objects augmented with sessionId, then continue with
the existing merged/compactedCount checks.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: aed27b2a-96c4-4ea9-8e55-f576b8151e4b

📥 Commits

Reviewing files that changed from the base of the PR and between 80ae27e and 5bad4f4.

📒 Files selected for processing (2)
  • scripts/__tests__/compact-session-history.test.ts
  • scripts/maintenance/compact-session-history.ts

Comment on lines +187 to +195
function buildMergedPayload(group: MessageRow[]): Record<string, unknown> | null {
const compacted = compactSessionHistoryNotifications(group.map((row) => ({
...(parsePayload(row.payload) as SessionHistoryNotification),
sessionId: row.session_id,
})));
const merged = compacted.history[0];
if (!merged || compacted.compactedCount !== 1) {
return null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Skip malformed payload groups too.

This now skips unsupported groups, but a single malformed session_messages.payload still throws inside parsePayload() and aborts the entire dry-run/apply before the null fallback is reached. That leaves legacy/corrupt rows as a full-stop failure mode for the maintenance command.

Proposed fix
 function buildMergedPayload(group: MessageRow[]): Record<string, unknown> | null {
-  const compacted = compactSessionHistoryNotifications(group.map((row) => ({
-    ...(parsePayload(row.payload) as SessionHistoryNotification),
-    sessionId: row.session_id,
-  })));
+  let notifications: SessionHistoryNotification[];
+  try {
+    notifications = group.map((row) => ({
+      ...(parsePayload(row.payload) as SessionHistoryNotification),
+      sessionId: row.session_id,
+    }));
+  } catch {
+    return null;
+  }
+
+  const compacted = compactSessionHistoryNotifications(notifications);
   const merged = compacted.history[0];
   if (!merged || compacted.compactedCount !== 1) {
     return null;
   }
   return merged as Record<string, unknown>;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function buildMergedPayload(group: MessageRow[]): Record<string, unknown> | null {
const compacted = compactSessionHistoryNotifications(group.map((row) => ({
...(parsePayload(row.payload) as SessionHistoryNotification),
sessionId: row.session_id,
})));
const merged = compacted.history[0];
if (!merged || compacted.compactedCount !== 1) {
return null;
}
function buildMergedPayload(group: MessageRow[]): Record<string, unknown> | null {
let notifications: SessionHistoryNotification[];
try {
notifications = group.map((row) => ({
...(parsePayload(row.payload) as SessionHistoryNotification),
sessionId: row.session_id,
}));
} catch {
return null;
}
const compacted = compactSessionHistoryNotifications(notifications);
const merged = compacted.history[0];
if (!merged || compacted.compactedCount !== 1) {
return null;
}
return merged as Record<string, unknown>;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/maintenance/compact-session-history.ts` around lines 187 - 195, The
buildMergedPayload function currently calls parsePayload on every row inside a
map, which lets a single malformed session_messages.payload throw and abort the
whole maintenance run; update buildMergedPayload to safely parse each row before
calling compactSessionHistoryNotifications: iterate over group and for each row
call parsePayload inside a try/catch (or a safeParse helper) and if any parse
fails, return null to skip that whole group; only build the array passed to
compactSessionHistoryNotifications from successfully parsed
SessionHistoryNotification objects augmented with sessionId, then continue with
the existing merged/compactedCount checks.

@phodal
phodal merged commit f96994b into phodal:main May 23, 2026
22 of 26 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