Skip to content

fix: resolve performance regression in transcript loading and session search#55

Merged
matt1398 merged 1 commit intomatt1398:mainfrom
proxikal:fix/perf-regression-agent-configs-and-search-stat
Feb 22, 2026
Merged

fix: resolve performance regression in transcript loading and session search#55
matt1398 merged 1 commit intomatt1398:mainfrom
proxikal:fix/perf-regression-agent-configs-and-search-stat

Conversation

@proxikal
Copy link
Contributor

Summary

This PR fixes two performance regressions introduced in PRs #50 and #53 that caused noticeable lag when opening transcripts and performing session searches in local mode.


Problem 1 — Transcript loading blocked by readAgentConfigs (introduced in #50)

Symptom: Clicking a transcript would hang with a loading spinner for a noticeable delay before anything rendered. This was most pronounced on macOS due to filesystem security checks on first directory access.

Root cause: fetchSessionDetail in sessionDetailSlice.ts awaited api.readAgentConfigs(projectRoot) on the critical render path — nothing could render until the IPC round-trip to read .claude/agents/ completed. Additionally, the project-level cache used a single module-level string, so switching between two projects triggered a new filesystem read on every switch.

Fix: Made the call fire-and-forget. The transcript now renders immediately; subagent type color badges update asynchronously once configs arrive. The cache key is set optimistically before the async call to prevent duplicate in-flight requests during rapid navigation.

// Before — blocks rendering
const configs = await api.readAgentConfigs(projectRoot);
if (requestGeneration !== sessionDetailFetchGeneration) return;
agentConfigsCachedForProject = projectRoot;
set({ agentConfigs: configs });

// After — non-blocking
agentConfigsCachedForProject = projectRoot; // optimistic, prevents duplicate fetches
api.readAgentConfigs(projectRoot)
  .then((configs) => { set({ agentConfigs: configs }); })
  .catch((err) => { logger.error(...); agentConfigsCachedForProject = ''; });

Problem 2 — Redundant stat() per session file on every search (introduced in #53)

Symptom: Session search felt slower than expected in local mode.

Root cause: SearchTextCache requires mtimeMs per session file for cache invalidation. LocalFileSystemProvider.readdir() returned FsDirent entries without mtimeMs (Node's readdir with withFileTypes doesn't include stat info). The new SessionSearcher.searchSessions() has a fallback — await fsProvider.stat(filePath) — which triggered for every session file on every search in local mode, adding N extra filesystem round-trips.

Fix: LocalFileSystemProvider.readdir() now stats all entries concurrently during the readdir call, populating mtimeMs on every returned entry. The stat fallback in SessionSearcher is never triggered for local mode.

// Before — mtimeMs always undefined in local mode → stat() called per file at search time
return entries.map((entry) => ({
  name: entry.name,
  isFile: () => entry.isFile(),
  isDirectory: () => entry.isDirectory(),
}));

// After — mtimeMs populated upfront via concurrent stats
return Promise.all(
  entries.map(async (entry) => {
    let mtimeMs: number | undefined;
    try {
      const stat = await fs.promises.stat(`${dirPath}/${entry.name}`);
      mtimeMs = stat.mtimeMs;
    } catch { /* ignore */ }
    return { name: entry.name, mtimeMs, isFile: ..., isDirectory: ... };
  })
);

Files Changed

File Change
src/renderer/store/slices/sessionDetailSlice.ts Made readAgentConfigs fire-and-forget with optimistic cache key
src/main/services/infrastructure/LocalFileSystemProvider.ts Populate mtimeMs in readdir() via concurrent stat calls

Test Plan

  • Open the app and click through several projects — session lists should load without delay
  • Click a transcript — content should render immediately (no hang before the loading spinner resolves)
  • Switch between two different projects and open a transcript in each — no regression on second project
  • Run a session search — results should return without noticeable extra delay in local mode
  • Subagent color badges still render correctly (may appear briefly without color, then update)

🤖 Generated with Claude Code

Two performance regressions introduced in recent PRs:

1. readAgentConfigs blocked transcript rendering (PR matt1398#50)
   The agent config IPC call was awaited on the critical path of
   fetchSessionDetail, preventing any transcript data from rendering
   until the filesystem read completed. On macOS this was especially
   noticeable due to security checks on first directory access.
   Fixed by making the call fire-and-forget: the transcript renders
   immediately and subagent color badges update asynchronously.
   Also set the project cache key optimistically before the async call
   to prevent duplicate in-flight requests on rapid navigation.

2. SessionSearcher stat()-called every session file on each search (PR matt1398#53)
   LocalFileSystemProvider.readdir() did not populate the optional
   mtimeMs field on FsDirent entries. The new SearchTextCache-based
   SessionSearcher fell back to an individual fsProvider.stat() call
   per session file when mtimeMs was missing, adding N extra filesystem
   round-trips on every search in local mode.
   Fixed by statting all entries concurrently inside readdir(), so
   mtimeMs is always populated and the stat fallback is never triggered.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@proxikal
Copy link
Contributor Author

Closing to re-submit after running full quality gates (typecheck, lint, test, build).

@proxikal proxikal closed this Feb 21, 2026
@proxikal proxikal reopened this Feb 21, 2026
@proxikal
Copy link
Contributor Author

Quality gates verified before re-opening: pnpm typecheckpnpm lintpnpm test (650 tests passing) ✅ pnpm build

@cesarafonseca
Copy link
Contributor

cesarafonseca commented Feb 21, 2026

@proxikal The performance issue you're seeing is likely not caused by the PRs itself.

There's a known problem where upgrading from one version to another can leave stale references behind (possibly in ~/Library/Application Support/ or ~/Library/Caches/ on macOS) that cause a significant lag spike.

I experienced the same thing upgrading from 0.4.2 to 0.4.3 and 0.4.3 to 0.4.4 for example.

This points to a version upgrade/migration issue rather than a problem with the code as implemented.

For reference, I did a clean install (both downloading the DMG directly and installing via Homebrew) and it worked perfectly with no lag.

@matt1398 FYI.

@proxikal
Copy link
Contributor Author

Thank you @cesarafonseca for the information. I'm going to figure out how to fix that locally so i can have a smooth migration experience, i think the best way (personally) would be a version-stamp check on startup before the window opens, compare the stored version against the current app.getVersion(). If they differ, wipe the Code Cache and GPUCache directories and then Electron recreates them automatically, then update the stamp. A little hackey though.

@matt1398 matt1398 merged commit 94a6599 into matt1398:main Feb 22, 2026
3 checks passed
@proxikal proxikal deleted the fix/perf-regression-agent-configs-and-search-stat branch February 23, 2026 00:09
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.

3 participants