Skip to content

feat: JSONL title cache + live/project session sync - #17

Merged
aterrylu merged 3 commits into
mainfrom
terry/title-cache-and-sync
Mar 10, 2026
Merged

feat: JSONL title cache + live/project session sync#17
aterrylu merged 3 commits into
mainfrom
terry/title-cache-and-sync

Conversation

@aterrylu

Copy link
Copy Markdown
Owner

Summary

  • JSONL title cache — server-side workaround for the Claude Agent SDK's unreliable customTitle (64KB head/tail buffer limitation). Reads actual JSONL files with mtime-based caching and efficient tail-first scanning.
  • Live ↔ project session sync — live session names update when the underlying project session is /renamed in Claude Code
  • Green status dots on project sessions that have active live sessions
  • Review cleanup — N+1 resolveProjectDir fix, double stat elimination, Sidebar useMemo for O(1) lookups, cache pruning, title marker constants

Details

The SDK's listSessions() reads only 64KB head/tail of each session JSONL file. When sessions grow, customTitle entries fall outside that window and disappear. This is a well-known bug with 12+ linked issues and no fix from Anthropic yet.

Resolution order: SDK customTitle → mtime-validated cache → JSONL parse → SDK summary

Test plan

  • Start server, check /api/projects returns sessions
  • /rename a session in Claude Code, verify title appears in dashboard within 30s
  • Verify green dots appear on project sessions with active live sessions
  • Verify live session name updates after /rename

🤖 Generated with Claude Code

aterrylu and others added 3 commits March 10, 2026 12:39
- Extract isMac to shared utils/platform.ts (was duplicated in App.tsx + useTerminal.ts)
- Track and clean up all timers (scrollTimer, nudgeTimer, reconnectTimer) on dispose and reconnect
- Close stale WebSocket before creating new one in connect() — prevents orphaned connections
- Only auto-scroll to bottom when user is already at bottom — don't fight manual scrollback
- Move MAX_RETRY_DELAY to module level
- Make case "b" call toggleSidebar() directly instead of relying on event bubbling
- Add nudgeResize for TUI redraw on reconnect, with tracked timer
- Increase scrollback to 10k lines, server buffer to 1MB
- Replace O(n²) Array.shift() buffer trim with bulk splice()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Claude Agent SDK's listSessions() reads only 64KB head/tail of
session JSONL files, causing custom titles to disappear as sessions
grow. This adds a server-side title cache that reads the actual files
with mtime-based invalidation, plus syncs live session names with
project session titles and shows green status dots on project sessions
that have active live sessions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix N+1 resolveProjectDir: group by cwd, resolve each once
- Eliminate double stat: single file handle for cache check + read
- Sidebar flatMap O(L*P) → useMemo Map lookup O(1)
- Prune orphaned cache entries on each batch call
- Extract title marker constants, skip already-scanned regions in phase 3
- Stream scan now only reads the middle gap [head, size-tail]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@aterrylu
aterrylu enabled auto-merge (squash) March 10, 2026 23:52
if (title) {
cache.set(sessionId, { title, mtimeMs });
} else {
cache.delete(sessionId);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Warning

Problem: cache.delete(sessionId) when extractTitle returns null means sessions with no custom title are re-scanned from disk on every poll cycle (every 30s). The mtime check on line 233 only short-circuits when a CacheEntry exists, so untitled sessions never get one.

Why it matters: In a project with many sessions that have never been renamed (common for exploratory sessions), every /api/projects poll opens, stats, and reads up to 512KB from each of those JSONL files. Even with OS page-cache, the repeated fh.stat() + buffer allocation adds up. A user with 50 untitled sessions burns ~150 syscalls + 25MB of buffer allocations per 30s poll.

Suggested fix:

// Store a sentinel so mtime-validated no-title is also cached
cache.set(sessionId, { title: "", mtimeMs });

Then in the early-return check:

const cached = cache.get(sessionId);
if (cached && cached.mtimeMs === mtimeMs) {
  return cached.title || null;  // "" → null, title → title
}

Also update batchGetTitles cache-check path to skip calling getCachedTitle when the mtime-matched entry is empty (title won't have appeared without an mtime change, so the cache hit is still valid).

@nox-0x nox-0x left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Solid implementation — the tail-first + mtime-cached JSONL scanning is the right approach to work around the SDK limitation, and the three-phase extraction logic correctly handles the head/middle/tail coverage for arbitrarily large files. The useMemo Sidebar optimization and buffer-trimming O(n²)→splice fix are both good cleanup. One warning flagged inline: sessions without custom titles bypass the mtime cache and get re-scanned from disk every poll — store a sentinel empty string in CacheEntry to fix. Not a blocker for merging.

@aterrylu
aterrylu merged commit d4b8ed8 into main Mar 10, 2026
0 of 2 checks passed
@aterrylu
aterrylu deleted the terry/title-cache-and-sync branch March 10, 2026 23:55
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