Skip to content

feat(sessions): Batch 1 — full-prompt search, match snippets, junk fold, highlight fix#132

Open
grimmerk wants to merge 5 commits into
mainfrom
feat/session-search-noise
Open

feat(sessions): Batch 1 — full-prompt search, match snippets, junk fold, highlight fix#132
grimmerk wants to merge 5 commits into
mainfrom
feat/session-search-noise

Conversation

@grimmerk

@grimmerk grimmerk commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

Session-finding Batch 1 — "search & noise" (plan: docs/session-finding-plan.md, included in this PR). Fixes #131.

1. Full search: ALL sessions × ALL user prompts (fixes #131)

Before: the search box only filtered the ~100 loaded sessions' visible fields (filterSessionsLocally), and the main-side searchClaudeSessions (500-pool) was dead code — ~314 of 414 sessions were unfindable.

Now, dual-path union:

  • Main-side deep search (rewritten searchClaudeSessions): scans every session's every prompt (from history.jsonl, all accounts), debounced 180ms, stale-response guarded. Prompt text stays in the main process (promptsBySession map rebuilt with the existing 5s session cache) — only small snippets cross IPC.
  • Renderer local filter (unchanged) still covers enrichment-only fields: custom title, branch, PR link, last AI reply, terminal badge.
  • Deep matches beyond the loaded list are appended with lazy enrichment (both bounded by the deep-search result cap, 100), everything re-sorts by recency.

2. Matched-prompt snippet line

When the hit is in a middle prompt (invisible in the row), the row shows ⌕ #N …±40 chars context… so you can see why it matched. Suppressed when it would duplicate the visible first/last lines (respects the first/last/both display-mode setting).

3. High-contrast search highlight

All 9 react-highlight-words sites now share one amber-background/dark-text style (SEARCH_HIGHLIGHT_STYLE). The old per-field translucent styles were near-invisible on colored text.

4. Minor-session folding

Closed, untitled, PR-less sessions with ≤2 messages collapse into an expandable ▸ N minor sessions row at the bottom (recency order preserved inside). Search always shows everything. Conservative: unknown messageCount and active sessions never fold. Manual per-session hide arrives with the pins PR (Batch 1 PR-2).

Implementation notes

  • New pure module src/session-search.ts (no fs/electron): matchesAllWords / extractSnippet / findPromptMatch / isMinorSession — unit-testable.
  • searchClaudeSessions return shape changed ({sessions, snippets}); it was previously uncalled, so no consumers were affected. IPC handler/preload are passthrough; electron-api.d.ts updated.
  • Keyboard navigation (↑↓/PageUp/PageDown/Enter) switched to the displayed list so indexes stay aligned with the fold.
  • Not covered here (by design): closed VS Code sessions' prompts aren't in history.jsonl — full transcript search is Batch 2 (FTS5, plan §5.2).

Tests

  • 10 new unit tests for the pure search/fold helpers (AND semantics, 2-char CJK match, snippet ellipses/whitespace, prompt-index attribution, conservative fold predicate) — 45 total pass.
  • npx tsc --noEmit clean.

🤖 On behalf of @grimmerk — generated with Claude Code


Summary by cubic

Adds full‑prompt search across all sessions with clearer matches and less noise in the Sessions switcher. You can now find any session by any user prompt and see why it matched; small, one‑shot sessions fold out of the way. Fixes #131.

  • New Features

    • Search scans all sessions and all user prompts in the main process, unions with the local field filter, and appends deep matches with lazy enrichment (debounced 180ms).
    • Rows show a matched‑prompt snippet: ⌕ #N …context… when the hit isn’t already visible; suppressed if it duplicates first/last lines.
    • Unified high‑contrast search highlight across all react-highlight-words sites for better readability.
    • Minor‑session folding: closed, untitled, PR‑less sessions with ≤2 messages collapse into an expandable “minor sessions” row; search always shows everything.
  • Bug Fixes

    • Prevented focus‑triggered list resets by reading the current search value from a ref (no stale closure clearing the filtered list).
    • Fold only after the first active‑session detection so a just‑started session never folds away at app start.
    • Deep‑match enrichment no longer silently caps; aligned with the append path and bounded by the search result cap (100).
    • Stopped random title/branch dropouts by batching enrichment to 10 sessions at a time, raising exec timeouts to 5s, and reading branch from the last 50 transcript lines.

Written for commit d07c385. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Search now covers all sessions and user prompts, including sessions beyond the currently loaded list.
    • Results show contextual snippets explaining why a session matched.
    • Added debounced search and improved, high-contrast highlighting.
    • Collapsed short, inactive one-off sessions into an expandable “Minor sessions” row.
  • Documentation

    • Updated the README and changelog to describe expanded search coverage, contextual matches, and session grouping.
    • Released as version 1.0.83.

- Search ALL sessions x ALL user prompts main-side (fixes #131);
  matches beyond the loaded 100 append with lazy enrichment
- Matched-prompt snippet line (prompt #N + context) when the hit
  isn't visible in the row's first/last lines
- Unified high-contrast amber search highlight (9 sites)
- Fold minor sessions (<=2 msgs, untitled, no PR, closed) into an
  expandable row
- New pure module session-search.ts + 10 unit tests (45 total)
- Plan doc: docs/session-finding-plan-zh-tw.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Session search now scans all sessions and prompts in the main process, returns contextual match snippets, and merges deep results into the switcher. The UI adds consistent highlighting, minor-session folding, debounced search coordination, lazy enrichment, and updated documentation and release metadata.

Changes

Session search experience

Layer / File(s) Summary
Prompt matching and folding helpers
src/session-search.ts, src/session-search.test.ts
Adds multi-word matching, contextual snippet extraction, first-match detection, minor-session classification, and unit coverage for these behaviors.
Main-process prompt search
src/claude-session-utility.ts, src/electron-api.d.ts
Caches prompt display text while reading history, searches all sessions and prompts, and exposes structured session and snippet results through the typed IPC API.
Deep search and session list rendering
src/switcher-ui.tsx
Debounces deep searches, merges and enriches matches outside the loaded list, folds minor sessions, updates keyboard navigation, and renders unified highlights and matched snippets.
Search behavior documentation and release metadata
README.md, CHANGELOG.md, docs/session-finding-plan.md, package.json
Documents expanded search, snippets, folding, implementation constraints, planned work, and version 1.0.83. Таблица?

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SwitcherUI
  participant ElectronAPI
  participant searchClaudeSessions
  participant history.jsonl
  SwitcherUI->>ElectronAPI: schedule debounced query
  ElectronAPI->>searchClaudeSessions: request deep session search
  searchClaudeSessions->>history.jsonl: read sessions and prompt text
  history.jsonl-->>searchClaudeSessions: session and prompt data
  searchClaudeSessions-->>ElectronAPI: matched sessions and snippets
  ElectronAPI-->>SwitcherUI: deep results
  SwitcherUI->>SwitcherUI: merge, enrich, fold, and highlight results
Loading

Possibly related PRs

  • grimmerk/codev#98: Overlaps the session-row assistant-response rendering path in src/switcher-ui.tsx.
  • grimmerk/codev#120: Overlaps search state clearing and focus-reset handling in src/switcher-ui.tsx.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements #131 by searching all sessions and prompts in the main process, returning snippets, and merging deep matches with renderer filtering.
Out of Scope Changes check ✅ Passed I don't see unrelated changes; the version bump, README, changelog, and plan doc all support the session-finding work.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main Batch 1 changes: full-prompt search, match snippets, junk-session folding, and highlight improvements.
✨ 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 feat/session-search-noise

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.

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

🧹 Nitpick comments (2)
src/electron-api.d.ts (1)

133-139: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Typed payload is correct; consider sharing the shape instead of triple-declaring it.

The snippets record shape ({ snippet, promptIndex, isLastPrompt }) is duplicated here, as SessionSearchMatch in claude-session-utility.ts, and inline in switcher-ui.tsx's searchSnippets state. A type-only import (erased at compile time, so it's safe across the main/renderer boundary) would give a single source of truth and catch drift at compile time instead of silently at runtime.

♻️ Share the type instead of duplicating it
+import type { SessionSearchMatch } from './claude-session-utility';
+
   searchClaudeSessions: (query: string) => Promise<{
     sessions: any[];
-    snippets: Record<
-      string,
-      { snippet: string; promptIndex: number; isLastPrompt: boolean }
-    >;
+    snippets: Record<string, SessionSearchMatch>;
   }>;
🤖 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/electron-api.d.ts` around lines 133 - 139, Reuse the existing
SessionSearchMatch type from claude-session-utility.ts for the snippets record
value in searchClaudeSessions, and update switcher-ui.tsx’s searchSnippets state
to use the same shared type. Remove the duplicated inline `{ snippet,
promptIndex, isLastPrompt }` declarations while preserving the current payload
shape.
src/claude-session-utility.ts (1)

215-239: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Full-corpus scan+lowercase runs synchronously on the main process for every search.

searchClaudeSessions rebuilds target (projectName + project + all prompts joined) and lowercases it for every session on every call — this is exactly the workload issue #131 wants to unlock (searching beyond the ~100 loaded sessions across all accounts/history). Since the function is synchronous and likely invoked via ipcMain.handle, this blocks the Electron main process for the full scan duration on each debounced keystroke. With large multi-account histories (the target use case), this could introduce noticeable UI jank/freezes app-wide (window drag, other IPC, status updates), not just in the switcher.

Two independent, low-cost mitigations:

  1. Cache pre-lowercased prompt text alongside promptsBySession (built once per cache refresh) instead of re-lowering the same megabytes of text on every keystroke-triggered search.
  2. If profiling on real large histories shows meaningful main-thread blocking, consider offloading the scan to a worker_threads worker or chunking it with setImmediate between session batches.

Please verify actual latency against realistic account sizes/history volumes before deciding whether (2) is necessary; (1) is cheap and worth doing regardless.

⚡ Minimal mitigation: cache lowercased prompts once per rebuild
-let promptsBySession: Map<string, string[]> = new Map();
+let promptsBySession: Map<string, string[]> = new Map();
+let promptsBySessionLower: Map<string, string[]> = new Map();

Populate promptsBySessionLower alongside promptsBySession in readClaudeSessions, then in searchClaudeSessions build target from the pre-lowered arrays instead of calling .toLowerCase() on the joined string each time.

🤖 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/claude-session-utility.ts` around lines 215 - 239, Cache lowercased
prompt text during the cache rebuild in readClaudeSessions by maintaining a
promptsBySessionLower structure alongside promptsBySession. Update
searchClaudeSessions to use the pre-lowered prompt array when constructing its
search target, avoiding repeated lowercasing of joined prompts on every query;
preserve the existing project-name/project matching and result behavior.
🤖 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 `@docs/session-finding-plan-zh-tw.md`:
- Line 125: Update the D3 `/pin` row in the Batch 3 table to include a third
cell matching the `項 | 內容 | 備註` header. Preserve the existing content in the
first two cells and add the appropriate notes cell, even if it is empty.

In `@src/claude-session-utility.ts`:
- Line 215: Run Prettier on searchClaudeSessions and the template-literal
assignment around the reported lines so both follow the project’s print-width
and line-break rules. Ensure any multiline arrays or objects in the affected
code use trailing commas.

In `@src/session-search.test.ts`:
- Line 12: Format the affected code in the session-search test using the
project’s Prettier configuration, including wrapping long string literals and
multi-argument calls and adding required trailing commas. Apply the changes to
the test cases around the target declaration and the referenced sections,
without altering their behavior.

In `@src/session-search.ts`:
- Around line 26-57: Format the return object in findPromptMatch so it fits the
configured print width, wrapping the snippet property as needed, and add the
required trailing comma to the object literal. Keep the matching logic
unchanged.

In `@src/switcher-ui.tsx`:
- Around line 411-429: Cap the non-duplicate deep matches appended by
applySearchFilter to MAX_APPENDED_DEEP_MATCHES before merging and sorting,
preserving the existing base results and ordering. In scheduleDeepSearch,
replace the appended-result literal 30 with the same MAX_APPENDED_DEEP_MATCHES
constant so display and enrichment limits remain synchronized.

---

Nitpick comments:
In `@src/claude-session-utility.ts`:
- Around line 215-239: Cache lowercased prompt text during the cache rebuild in
readClaudeSessions by maintaining a promptsBySessionLower structure alongside
promptsBySession. Update searchClaudeSessions to use the pre-lowered prompt
array when constructing its search target, avoiding repeated lowercasing of
joined prompts on every query; preserve the existing project-name/project
matching and result behavior.

In `@src/electron-api.d.ts`:
- Around line 133-139: Reuse the existing SessionSearchMatch type from
claude-session-utility.ts for the snippets record value in searchClaudeSessions,
and update switcher-ui.tsx’s searchSnippets state to use the same shared type.
Remove the duplicated inline `{ snippet, promptIndex, isLastPrompt }`
declarations while preserving the current payload shape.
🪄 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: f9209980-4fc6-4fe1-bf29-2c30c5200fe7

📥 Commits

Reviewing files that changed from the base of the PR and between 88eb84d and 1ef13ea.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • README.md
  • docs/session-finding-plan-zh-tw.md
  • package.json
  • src/claude-session-utility.ts
  • src/electron-api.d.ts
  • src/session-search.test.ts
  • src/session-search.ts
  • src/switcher-ui.tsx

Comment thread docs/session-finding-plan-zh-tw.md Outdated
Comment thread src/claude-session-utility.ts Outdated
Comment thread src/session-search.test.ts Outdated
Comment thread src/session-search.ts
Comment thread src/switcher-ui.tsx

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 9 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/switcher-ui.tsx Outdated
Comment thread docs/session-finding-plan-zh-tw.md Outdated
Comment thread src/switcher-ui.tsx
grimmerk and others added 2 commits July 12, 2026 17:34
- Read search value via ref in fetchClaudeSessions (stale closure
  could clear the filtered list on window focus) [cubic P1]
- Gate minor-session folding on first active-detection completion
  so a just-started session never folds at app start [cubic P2]
- Un-cap deep-match enrichment (was 30) to match the uncapped
  append; both now bounded by the search result cap of 100
  [CodeRabbit Major, resolved toward no-silent-caps]
- Prettier: new files wholesale; changed lines only in
  claude-session-utility.ts / switcher-ui.tsx
- Docs: plan status header reflects PR-1 in flight; D3 table row
  third cell [cubic P2 / CodeRabbit Minor]

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add docs/session-finding-plan.md (full English translation,
  same status/decisions/gotchas)
- Untrack the zh-TW copy (stays as a local working copy; note
  added pointing at the English canonical version)
- CHANGELOG plan reference updated

Co-Authored-By: Claude Fable 5 <noreply@anthropic.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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/switcher-ui.tsx (1)

793-797: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

onSessionStatusesUpdated still uses filterSessionsLocally — deep matches vanish during status updates.

This PR updated all other search-aware setSessions call sites (lines 509, 550, 592) to use applySearchFilter, but missed this one. When a session status update arrives during an active search, filterSessionsLocally filters out deep-matched sessions (matched by prompt text in the main process, not by local enrichment fields). Deep matches disappear and don't reappear until the user types again, undermining the PR's core objective.

🔧 Use `applySearchFilter` for consistency
             setSessions((prev: any[]) => {
               const updated = updateSessions(prev);
               const search = sessionSearchRef2.current;
-              return search.trim() ? filterSessionsLocally(updated, search) : updated;
+              return search.trim() ? applySearchFilter(updated, search) : updated;
             });
🤖 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/switcher-ui.tsx` around lines 793 - 797, Update the search-aware
setSessions callback in onSessionStatusesUpdated to use applySearchFilter
instead of filterSessionsLocally, preserving the existing updateSessions flow
and current search value handling so deep-matched sessions remain visible during
status updates.
🧹 Nitpick comments (1)
src/switcher-ui.tsx (1)

1604-1611: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fold/expand rows lack keyboard interaction support.

Both the expand row (line 1604) and the collapse row (line 1371) only have onClick handlers. Keyboard users cannot toggle minor-session folding. Consider adding tabIndex={0}, role="button", and an onKeyDown handler for Enter/Space.

⌨️ Add keyboard support to the expand row
               {!isSearchingSessions && !minorsExpanded && minorSessions.length > 0 && (
                 <div
+                  tabIndex={0}
+                  role="button"
+                  aria-label={`Expand ${minorSessions.length} minor sessions`}
                   onClick={() => setMinorsExpanded(true)}
+                  onKeyDown={(e) => {
+                    if (e.key === 'Enter' || e.key === ' ') {
+                      e.preventDefault();
+                      setMinorsExpanded(true);
+                    }
+                  }}
                   style={{ padding: '6px 10px 8px 24px', color: '`#777`', fontSize: '12px', cursor: 'pointer' }}
                 >
                   ▸ {minorSessions.length} minor sessions (≤2 msgs, untitled)
                 </div>
               )}
🤖 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/switcher-ui.tsx` around lines 1604 - 1611, Update the minor-session
expand row near the isSearchingSessions/minorsExpanded conditional to add
keyboard accessibility with tabIndex={0}, role="button", and an onKeyDown
handler that triggers setMinorsExpanded(true) for Enter and Space. Apply the
same interaction support to the collapse row's existing toggle near the
minorsExpanded collapse control, preserving their current click behavior.
🤖 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/switcher-ui.tsx`:
- Around line 454-456: Apply Prettier formatting to the session-loading code
around the loaded and appended declarations, and the related code at the second
flagged location, so it matches the repository’s formatting rules and passes
prettier/prettier ESLint checks. Preserve the existing behavior and logic.

---

Outside diff comments:
In `@src/switcher-ui.tsx`:
- Around line 793-797: Update the search-aware setSessions callback in
onSessionStatusesUpdated to use applySearchFilter instead of
filterSessionsLocally, preserving the existing updateSessions flow and current
search value handling so deep-matched sessions remain visible during status
updates.

---

Nitpick comments:
In `@src/switcher-ui.tsx`:
- Around line 1604-1611: Update the minor-session expand row near the
isSearchingSessions/minorsExpanded conditional to add keyboard accessibility
with tabIndex={0}, role="button", and an onKeyDown handler that triggers
setMinorsExpanded(true) for Enter and Space. Apply the same interaction support
to the collapse row's existing toggle near the minorsExpanded collapse control,
preserving their current click behavior.
🪄 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: 0a3b1b12-3367-43a8-ab3b-1b9af4599271

📥 Commits

Reviewing files that changed from the base of the PR and between 1ef13ea and 6fa6316.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • docs/session-finding-plan.md
  • src/claude-session-utility.ts
  • src/session-search.test.ts
  • src/session-search.ts
  • src/switcher-ui.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/session-search.test.ts
  • src/session-search.ts
  • src/claude-session-utility.ts

Comment thread src/switcher-ui.tsx Outdated
grimmerk and others added 2 commits July 12, 2026 23:51
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pre-existing bug surfaced during Batch 1 testing:

- Enrichment spawned ~400 concurrent exec greps (100 sessions x 4);
  the biggest transcripts regularly blew the silent 3s timeout
  (errors resolve to ''), so their title/branch vanished at random.
  Now batched 10 sessions at a time; timeout 3s -> 5s. Same batching
  applied to loadLastAssistantResponses (100 concurrent tails).
- Branch was read from the last 5 transcript lines; an active
  session's tail is often tool output with no gitBranch field
  (measured on a live 26MB session: tail -5 hit 0, tail -20 hit 12).
  Window widened to 50 lines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/claude-session-utility.ts">

<violation number="1" location="src/claude-session-utility.ts:1743">
P2: The batched enrichment in `loadSessionEnrichment` writes the cache timestamp using the value captured at function entry. With the new `runInBatches(…, 10)` approach, a large session list can take longer than the 5-second `CACHE_TTL_MS` to finish, so the just-populated cache is considered immediately stale and another full scan is triggered. Overlapping concurrent requests also start independent scans because there is no in-flight promise shared between callers.

Keep `titlesCacheTimestamp = Date.now()` instead of reusing the entry-time `now` so the TTL clock starts after work completes, and consider caching the in-flight promise so concurrent callers deduplicate.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

const branches = new Map<string, string>();
const prLinks = new Map<string, PRLinkInfo>();
const promises = sessions.map(async (session) => {
await runInBatches(sessions, 10, async (session) => {

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: The batched enrichment in loadSessionEnrichment writes the cache timestamp using the value captured at function entry. With the new runInBatches(…, 10) approach, a large session list can take longer than the 5-second CACHE_TTL_MS to finish, so the just-populated cache is considered immediately stale and another full scan is triggered. Overlapping concurrent requests also start independent scans because there is no in-flight promise shared between callers.

Keep titlesCacheTimestamp = Date.now() instead of reusing the entry-time now so the TTL clock starts after work completes, and consider caching the in-flight promise so concurrent callers deduplicate.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/claude-session-utility.ts, line 1743:

<comment>The batched enrichment in `loadSessionEnrichment` writes the cache timestamp using the value captured at function entry. With the new `runInBatches(…, 10)` approach, a large session list can take longer than the 5-second `CACHE_TTL_MS` to finish, so the just-populated cache is considered immediately stale and another full scan is triggered. Overlapping concurrent requests also start independent scans because there is no in-flight promise shared between callers.

Keep `titlesCacheTimestamp = Date.now()` instead of reusing the entry-time `now` so the TTL clock starts after work completes, and consider caching the in-flight promise so concurrent callers deduplicate.</comment>

<file context>
@@ -1718,15 +1732,15 @@ export const loadSessionEnrichment = async (sessions: ClaudeSession[]): Promise<
   const branches = new Map<string, string>();
   const prLinks = new Map<string, PRLinkInfo>();
-  const promises = sessions.map(async (session) => {
+  await runInBatches(sessions, 10, async (session) => {
     const encodedProject = session.project.replace(/[^a-zA-Z0-9-]/g, '-');
     const jsonlPath = path.join(
</file context>

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.

search: only the ~100 loaded sessions are searchable; main-side searchClaudeSessions (500-pool) is dead code

1 participant