Skip to content

feat: VS Code session support + real-time preview refresh - #103

Merged
grimmerk merged 25 commits into
mainfrom
feat/vscode-session-support
Apr 5, 2026
Merged

feat: VS Code session support + real-time preview refresh#103
grimmerk merged 25 commits into
mainfrom
feat/vscode-session-support

Conversation

@grimmerk

@grimmerk grimmerk commented Apr 5, 2026

Copy link
Copy Markdown
Owner

Summary

Add VS Code Claude Code session support to CodeV — detection, display, switching, resuming, and launching of VS Code-based Claude Code sessions. Also improves real-time session preview updates for all sessions.

Key Features

  • Detection: Active sessions from ~/.claude/sessions/ (entrypoint filter removed). Closed sessions via JSONL scan (~50ms for 218 files) + hooks index for skip optimization.
  • Display: [VSCODE] badge (active only), ai-title as display name fallback, status dots via hooks, <ide_> context blocks filtered from user messages.
  • Switch (active): URI handler vscode://anthropic.claude-code/open?session=<UUID> — instant, precise session tab switching.
  • Resume (closed): code <projectPath> + 2s delay + URI handler. Falls back to default terminal if Launch Terminal is not set to VS Code.
  • Settings: VS Code added to Launch Terminal dropdown.
  • Search: Terminal type (vscode/ghostty/etc.), ai-title, PR URLs all searchable. Badge + PR link highlighting on match.
  • Real-time preview: Last assistant msg, last user msg, and session order auto-update when session becomes idle (via refreshSessionPreview — single tail read for both).

Measured Latency

Scenario Time Reason
Active VS Code session switch Instant URI handler direct switch
Resume in already-open VS Code project ~1-2s code <path> activates + 2s delay + URI handler
Resume in new VS Code project ~3-5s VS Code opens new window + 2s delay + URI handler
JSONL scan (closed sessions, 218 files) ~50ms 4KB read per file, cached 30s

VS Code URI Handler Reference

URL Effect
vscode://anthropic.claude-code/open Open new Claude Code tab
vscode://anthropic.claude-code/open?session=<UUID> Switch to / resume session
vscode://anthropic.claude-code/open?prompt=<text> Open with pre-filled prompt

Requires Claude Code VS Code extension v2.1.72+ (released 2026-03-10).

Shared Algorithm (avoids duplicate reads)

Active/Closed VS Code sessionsreadVSCodeSessionFromJSONL() does parallel head -n 20 + tail -n 100 + grep -c:

  • First user prompt from head (skips <ide_> blocks)
  • Last user prompt + last assistant message from same tail
  • Message count from grep

Real-time refreshrefreshSessionPreview() does single tail -n 100 per session on idle:

  • Extracts both last user msg + last assistant msg
  • Updates session order (lastTimestamp) + re-sorts
  • No duplicate reads with loadLastAssistantResponses()

Architecture Layers

  1. Detection: detectActiveSessions() (active) + scanClosedVSCodeSessions() (closed, cached 30s)
  2. Hooks: $CLAUDE_CODE_ENTRYPOINT env var → writes vscode-sessions.jsonl index + skip optimization
  3. Display: Badge for active only, ai-title fallback, status dots, search highlighting
  4. Switch/Resume: URI handler for active; code <path> + delay + URI handler for closed
  5. Settings: VS Code in Launch Terminal dropdown
  6. Session cap: Total capped at 100 after merge + sort by timestamp
  7. Real-time refresh: fs.watch (debounced 50ms) → timestamp-based idle detection → refreshSessionPreview() (300ms delay) → update assistant msg + user msg + order
  8. Timestamp normalization: VS Code ISO strings → unix ms (fixes sort order)

See docs/vscode-session-support-design.md for full design.

Resolves item 5 of #66.

Test plan

  • Active VS Code sessions appear with [VSCODE] badge
  • Closed VS Code sessions appear (no badge, consistent with CLI)
  • ai-title shown as display name
  • Status dots work for VS Code sessions
  • Click active → switches to correct tab in VS Code
  • Click closed → resumes in VS Code (when Launch Terminal = VS Code)
  • Click closed → resumes in default terminal (when Launch Terminal != VS Code)
  • Search by vscode/ghostty/iterm2 filters by terminal type
  • Badge + PR link search highlighting
  • Session count capped at 100
  • Sorting correct (ISO string timestamps normalized to unix ms)
  • <ide_opened_file> blocks filtered from user message display
  • VS Code option in Settings → Launch Terminal dropdown
  • Real-time last assistant msg update on idle
  • Real-time last user msg update on idle
  • Real-time session order update on idle
  • CLI sessions unaffected (no regression)
  • Performance acceptable (fs.watch debounced, no lag)

🤖 On behalf of @grimmerk — generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Detect and badge active VS Code sessions ([VSCODE]) and allow switching/resuming (active & closed).
    • Real-time idle preview auto-refresh of last messages and session ordering.
    • Search can filter by terminal/IDE type.
  • Bug Fixes

    • Fixed timestamp sorting for reliable session order.
    • Improved VS Code preview to skip irrelevant context blocks.
  • UI/UX

    • PR badge repositioned; search highlighting applies to both badges.
    • Added "VS Code" as a launch terminal option.
  • Documentation

    • Added design doc for VS Code session support and resumption.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 5, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds VS Code Claude Code session support: detects active VS Code sessions with a [VSCODE] badge, discovers closed sessions via a JSONL hooks index and fallback scans, enables switching/resuming via VS Code URIs and code <path>, implements idle-driven real-time preview refresh (debounced fs.watch + single tail read), extends terminal-type filtering and search highlighting, and updates IPC/preload API surfaces.

Changes

Cohort / File(s) Summary
Documentation & Metadata
CHANGELOG.md, docs/vscode-session-support-design.md, package.json
Release notes for v1.0.67, new VS Code session design doc describing detection, index/JSONL scanning, URI resume flow, preview refresh mechanics, and version bump.
Session Core Utilities
src/claude-session-utility.ts
Added entrypoint field, new ActiveSessionResult return (activeMap, vscodeSessions, entrypoints), JSONL parsing helpers, readVSCodeSessionFromJSONL, scanClosedVSCodeSessions, refreshSessionPreview, openSessionInVSCode, caching for VS Code discovery, and updated detect/open flows for claude-vscode.
Type/API Surface
src/electron-api.d.ts, src/preload.ts
Changed detectActiveSessions() return shape and detectTerminalApps() signature (accepts optional entrypointMap). Exposed new preload APIs: scanClosedVSCodeSessions() and refreshSessionPreview().
Main Process & IPC
src/main.ts
Consume expanded detectActiveSessions result, convert entrypoints to maps, forward entrypointMap into terminal detection (short-circuiting for VS Code), persist status entries with timestamps, and add scan-closed-vscode-sessions and refresh-session-preview IPC handlers.
Status Hooks & Indexing
src/session-status-hooks.ts
Add VSCODE_INDEX_PATH and hook writes for VS Code (per-session .vs- marker and append to vscode-sessions.jsonl), readVSCodeIndex(), StatusEntry {status,timestamp}, change readAllStatuses()/watchStatusDir() to return timestamped entries, and add 50ms debounce to fs.watch.
UI: Session List, Search & Settings
src/switcher-ui.tsx, src/popup.tsx
Integrate structured active result and closed-VSCode scanning, extend search target with terminal/IDE badge token, preload assistant messages for closed VS Code sessions, add preview auto-refresh on idle with merge/update logic, adjust badge rendering/PR click behavior, and add vscode option to Launch Terminal settings.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Renderer (UI)
    participant Main as Main Process
    participant FS as File System
    participant VSCode as VS Code
    participant Hook as Status Hooks

    UI->>Main: detectActiveSessions()
    Main->>FS: read ~/.claude/projects and hooks index (~/.claude/codev-status/vscode-sessions.jsonl)
    Main->>Main: build activeMap, vscodeSessions, entrypoints
    Main->>UI: return { activeMap, vscodeSessions, entrypoints }

    UI->>Main: detectTerminalApps(activeMap, entrypointMap)
    Main->>Main: for entrypoint 'claude-vscode' -> return 'vscode'
    Main->>UI: return terminal app map

    UI->>Main: scanClosedVSCodeSessions(activeIds)
    Main->>FS: read vscode-sessions.jsonl, enumerate projects JSONL
    Main->>FS: parallel head/tail reads per session JSONL
    Main->>Main: extract last user/assistant messages, timestamps
    Main->>UI: return closed VS Code sessions array

    Note over UI: On idle status transition
    UI->>Main: refreshSessionPreview(sessions)
    Main->>FS: tail -n 100 per session JSONL
    Main->>Main: extract lastUserMessage, lastAssistantMessage
    Main->>UI: return preview map
    UI->>UI: merge into state, re-sort and re-filter sessions
Loading
sequenceDiagram
    participant UI as Renderer (UI)
    participant Main as Main Process
    participant VSCode as VS Code App
    participant FS as File System

    UI->>UI: user clicks "open" on VS Code session

    alt Active Session
        UI->>Main: openSessionInVSCode(sessionId)
        Main->>VSCode: open vscode://anthropic.claude-code/open?session=sessionId
        VSCode->>VSCode: resume active session
    else Closed Session
        UI->>Main: openSessionInVSCode(sessionId, projectPath)
        Main->>VSCode: code "<projectPath>"
        Main->>FS: wait for session activation / hooks update
        Main->>VSCode: open vscode://anthropic.claude-code/open?session=sessionId
        VSCode->>VSCode: resume closed session from JSONL
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 Hopping through code and JSONL tails,

VS Code sessions follow rabbit trails.
A fifty-mill wink, previews refreshed fast,
Badges and URIs make resumes last—
Hop on, dear devs, the switcher’s vast! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately reflects the main feature additions: VS Code session support (detection, display, switching, resuming) and real-time preview refresh functionality.

✏️ 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 feat/vscode-session-support

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

grimmerk and others added 4 commits April 5, 2026 16:46
- Remove entrypoint filter in detectActiveSessions()
- Add readVSCodeSessionFromJSONL() for session metadata
- Route VS Code sessions to URI handler (vscode://...)
- Add ai-title fallback in loadSessionEnrichment()
- Merge VS Code sessions into UI session list
- Pass entrypoints to detectTerminalApps() to skip process
  tree walk for VS Code sessions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Badge shows for both active (via terminalApps) and inactive
(via entrypoint field) VS Code sessions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
VS Code injects <ide_opened_file> context as the first text
block in user messages. Skip these to show actual user input.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
grimmerk and others added 11 commits April 5, 2026 17:19
- PR link badge now renders before terminal/VSCODE badge
  for consistent layout (terminal type always last)
- Search filter now includes terminal type (vscode, ghostty,
  iterm2, etc.) so users can filter by terminal

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When pasting a full PR URL as search query, the PR badge
now gets a highlight background even though the badge text
only shows 'PR #N'.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Scan ~/.claude/projects/ for closed VS Code sessions (50ms
  for 218 files via 4KB entrypoint check)
- Hooks index: write VS Code sessions to vscode-sessions.jsonl
  via $CLAUDE_CODE_ENTRYPOINT env var, skip known IDs in scan
- Shared extractUserText() + parseUserMessageFromLines() for
  IDE context filtering across active + closed sessions
- head/tail parallel reads for first/last prompt (shared
  pattern with loadLastAssistantResponses)
- Async SWR merge into session list with enrichment loading
- 30s cache TTL for closed session scan

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tail reads

- readVSCodeSessionFromJSONL() now async with head -n 20 +
  tail -n 100 + grep -c in parallel (no more full file read)
- Single tail read extracts both last user prompt AND last
  assistant message via shared parseAssistantMessageFromLines()
- UI uses pre-loaded assistant responses instead of calling
  loadLastAssistantResponses() again for VS Code sessions
- scanClosedVSCodeSessions() reuses readVSCodeSessionFromJSONL()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Directory name decode is lossy (both / and - map to -), causing
wrong project paths. Now reads cwd from JSONL entry content
via head read. Falls back to hooks index cwd if available.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add VS Code option to Launch Terminal dropdown in Settings
- Remove terminal/IDE badge for closed sessions (consistent
  with existing CLI behavior, avoids stale badges)
- Handle 'vscode' as launch terminal: route to URI handler
- Active VS Code sessions still use URI handler for switching

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- openSessionInVSCode: open project folder first for closed
  sessions before URI handler resume (2s delay for VS Code
  to load workspace)
- Convert ISO string timestamps to unix ms in JSONL reader
  (fixes sort order when mixing CLI + VS Code sessions)
- Cap total session count at 100 after merging VS Code sessions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a session transitions to 'idle' (Stop hook fires), re-fetch
its last assistant response via loadLastAssistantResponses().
This ensures the blue preview text updates in real-time without
requiring the user to switch tabs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
grimmerk and others added 7 commits April 5, 2026 19:21
The onSessionStatusesUpdated callback captured a stale
allSessions from the initial useEffect closure. Added
allSessionsRef to keep current value accessible.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previous approach (working→idle transition) was unreliable
because fs.watch can coalesce rapid writes, causing React
to only see the final 'idle' state without seeing 'working'.

Now compares status file timestamp with last fetch time —
works regardless of missed intermediate states.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Temporary logging to diagnose intermittent assistant message
update. Added 300ms delay after idle detection to ensure JSONL
is fully flushed before tail read.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- refreshSessionPreview(): single tail -n 100 reads both last
  user message and last assistant message (no duplicate reads)
- Status handler updates lastTimestamp + re-sorts on idle
- fs.watch debounced to 50ms (was firing 3-6x per event)
- Remove debug logging

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Step 5 was calling detectActiveSessions() again (already called
in Step 3). Now reuses activeMap from Step 3. Also merges
active + closed VS Code enrichment into single call.
Removes debug console.log.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@grimmerk
grimmerk marked this pull request as ready for review April 5, 2026 12:43
@grimmerk grimmerk changed the title feat: VS Code Claude Code session support feat: VS Code session support + real-time preview refresh Apr 5, 2026

@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

Caution

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

⚠️ Outside diff range comments (1)
src/electron-api.d.ts (1)

46-46: ⚠️ Potential issue | 🟠 Major

Type declaration does not match actual return value.

This line declares getSessionStatuses returns Record<string, string | null>, but main.ts line 1862 now returns objects with { status, timestamp } shape. This causes switcher-ui.tsx to implement runtime type guards. Update this declaration to match the actual return type.

Update type declaration
-  getSessionStatuses: () => Promise<Record<string, string | null>>;
+  getSessionStatuses: () => Promise<Record<string, { status: string | null; timestamp: number } | string | null>>;

Or if the intention is to always return the object shape:

-  getSessionStatuses: () => Promise<Record<string, string | null>>;
+  getSessionStatuses: () => Promise<Record<string, { status: string | null; timestamp: number }>>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/electron-api.d.ts` at line 46, The type for getSessionStatuses is
incorrect: update the declaration in electron-api.d.ts so it matches the actual
return shape from main.ts (objects with status and timestamp) instead of
string|null; e.g., replace the current Promise<Record<string, string | null>>
return type with a record whose values are an object type containing status and
timestamp (both nullable), or define a named interface (e.g., SessionStatus {
status: string | null; timestamp: string | null }) and use
Promise<Record<string, SessionStatus>> for getSessionStatuses to keep types
consistent with the runtime value.
🧹 Nitpick comments (5)
src/session-status-hooks.ts (2)

208-211: Interface naming convention: prefix with 'I'.

Per coding guidelines, interface names should be prefixed with 'I'.

Suggested rename
-export interface StatusEntry {
+export interface IStatusEntry {
   status: SessionStatus;
   timestamp: number; // unix seconds from status file
 }

Update references in readAllStatuses, watchStatusDir, and src/main.ts accordingly.

As per coding guidelines: "Prefix interface names with 'I' (e.g., IWindow)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/session-status-hooks.ts` around lines 208 - 211, Rename the exported
interface StatusEntry to IStatusEntry and update all usages accordingly; change
the declaration export interface StatusEntry { ... } to export interface
IStatusEntry { status: SessionStatus; timestamp: number; } and update any
references in readAllStatuses, watchStatusDir and in main (previously
importing/using StatusEntry) to use IStatusEntry so types and imports remain
consistent.

190-206: Silent failures in JSONL parsing may hide data corruption.

Empty catch blocks at lines 202 and 204 silently swallow JSON parse errors and file read errors. Consider logging malformed lines at debug level to aid troubleshooting.

Add debug logging for parse failures
       try {
         const entry = JSON.parse(line);
         if (entry.sessionId && entry.cwd) {
           index.set(entry.sessionId, entry.cwd);
         }
-      } catch {}
+      } catch (e) {
+        // Skip malformed lines — may occur if hook write was interrupted
+      }
     }
-  } catch {}
+  } catch (e) {
+    // Index file doesn't exist or isn't readable — return empty map
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/session-status-hooks.ts` around lines 190 - 206, readVSCodeIndex
currently swallows file read and JSON parse errors (empty catch blocks) which
hides corrupted lines; update readVSCodeIndex to log failures instead of
silently ignoring them: when fs.readFileSync(VSCODE_INDEX_PATH) throws, catch
and log the error (use the existing logger if available, e.g.
processLogger.error, or console.error) and return the empty Map; when JSON.parse
fails for a line, catch and log a debug-level message that includes the
offending line content and the parse error (but continue processing other
lines); keep the function behavior of returning a Map of valid sessionId→cwd
entries.
docs/vscode-session-support-design.md (1)

55-57: Add language specifier to fenced code block.

The code block starting at line 55 should have a language specifier for proper syntax highlighting.

Add bash language specifier
-```
+```bash
 open "vscode://anthropic.claude-code/open?session=<UUID>"
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @docs/vscode-session-support-design.md around lines 55 - 57, The fenced code
block containing the command open
"vscode://anthropic.claude-code/open?session=" should include a language
specifier for syntax highlighting; update the opening fence from tobash
so the snippet is rendered as bash (e.g., change the block around the open
"vscode://anthropic.claude-code/open?session=" line to use ```bash).


</details>

</blockquote></details>
<details>
<summary>src/claude-session-utility.ts (1)</summary><blockquote>

`796-808`: **Potential resource leak if exception occurs between openSync and closeSync.**

If `fs.readSync` throws, the file descriptor won't be closed. While rare, this could leak FDs over time.

<details>
<summary>♻️ Wrap in try/finally</summary>

```diff
       try {
         const fd = fs.openSync(filePath, 'r');
-        const buf = new Uint8Array(4096);
-        const bytesRead = fs.readSync(fd, buf, 0, 4096, 0);
-        fs.closeSync(fd);
+        try {
+          const buf = new Uint8Array(4096);
+          const bytesRead = fs.readSync(fd, buf, 0, 4096, 0);
+          const chunk = Buffer.from(buf.buffer, 0, bytesRead).toString('utf-8');
+          if (chunk.includes('"entrypoint":"claude-vscode"') || chunk.includes('"entrypoint": "claude-vscode"')) {
+            vsCodeFiles.push({ sessionId, cwd: '', jsonlPath: filePath });
+          }
+        } finally {
+          fs.closeSync(fd);
+        }
-        const chunk = Buffer.from(buf.buffer, 0, bytesRead).toString('utf-8');
-        if (chunk.includes('"entrypoint":"claude-vscode"') || chunk.includes('"entrypoint": "claude-vscode"')) {
-          vsCodeFiles.push({ sessionId, cwd: '', jsonlPath: filePath });
-        }
       } catch {}
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@src/claude-session-utility.ts` around lines 796 - 808, The try block that
opens and reads the file can leak the file descriptor if an exception occurs
between fs.openSync and fs.closeSync; modify the code around filePath so fd is
declared outside (e.g. let fd), then open the file inside a try and always close
it in a finally block (check fd is a valid number before calling fs.closeSync)
while keeping the existing logic that builds buf, bytesRead, chunk and pushes
into vsCodeFiles when the entrypoint matches; alternatively replace the
open/read/close pattern with a single fs.readFileSync call to avoid manual FD
management.
```

</details>

</blockquote></details>
<details>
<summary>src/switcher-ui.tsx (1)</summary><blockquote>

`396-493`: **Missing error handling in chained async operations.**

Multiple `.then()` chains lack `.catch()` handlers. If any of these IPC calls fail (e.g., `detectActiveSessions`, `scanClosedVSCodeSessions`, `loadSessionEnrichment`), the errors will be silently swallowed and the UI may end up in an inconsistent state.

Consider adding error handling, at minimum logging errors:

<details>
<summary>Example error handling pattern</summary>

```diff
     window.electronAPI.detectActiveSessions().then((result: any) => {
       // ... existing code ...
+    }).catch((err: any) => {
+      console.error('[fetchClaudeSessions] detectActiveSessions failed:', err);
     });
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@src/switcher-ui.tsx` around lines 396 - 493, The chained IPC promises
(detectActiveSessions, detectTerminalApps, scanClosedVSCodeSessions,
loadSessionEnrichment) lack error handling; add .catch() handlers to each
promise chain to log the error via your logger (or console.error) and gracefully
update state (e.g., avoid mutating allSessionsRef, call
setAllSessions/setSessions with safe defaults or previous state, and avoid
calling subsequent dependent operations like detectTerminalApps or
loadSessionEnrichment when their prerequisite failed). Specifically update the
promises returned by window.electronAPI.detectActiveSessions(),
window.electronAPI.detectTerminalApps(...),
window.electronAPI.scanClosedVSCodeSessions(activeIds) and
window.electronAPI.loadSessionEnrichment(allVSCode) to attach .catch(...) that
logs the error and performs minimal safe state updates (using setAllSessions,
setSessions, setAssistantResponses, setTerminalApps as appropriate) so the UI
remains consistent.
```

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against the current code and only fix it if needed.

Inline comments:
In @src/claude-session-utility.ts:

  • Around line 1082-1115: The function refreshSessionPreview currently returns a
    Map which does not serialize over IPC; change its return type to
    Promise<Record<string,{ lastUserMessage: string; lastAssistantMessage: string
    }>> and build/return a plain object instead of a Map (in refreshSessionPreview
    replace the Map creation and results.set(...) with a plain object literal,
    assign results[session.sessionId] = { ... } where you currently set the Map, and
    return that object); update any local variable types (e.g., results) and keep
    the existing logic for reading files and parsing via parseUserMessageFromLines
    and parseAssistantMessageFromLines so switcher-ui.tsx can use
    Object.entries(previews) as expected.
  • Around line 26-30: ActiveSessionResult currently uses Map<string, ...> which
    won't survive Electron IPC; update the contract so the IPC payload is plain
    objects: change ActiveSessionResult to use Record<string, number> for activeMap
    and Record<string, string> for entrypoints, and ensure detectActiveSessions (the
    function that returns this result) converts any internal Map instances to plain
    objects (e.g., via Object.fromEntries or manual iteration) before returning;
    also adjust consumers like the destructuring in switcher-ui.tsx to match the new
    Record types if needed.
  • Around line 1137-1146: The exec call using a shell-interpolated projectPath in
    openSessionInVSCode is vulnerable to shell injection and the fixed 2s setTimeout
    is brittle; replace exec(code "${projectPath}", ...) with a non-shell
    invocation (use child_process.spawn or execFile with args array) passing
    projectPath as a single argument to eliminate shell interpretation, capture the
    child process object to detect when Code has started (listen for 'error'/'close'
    events), and remove the fixed setTimeout by implementing a short retry loop to
    open the vscode://anthropic.claude-code/open?session=${sessionId} URI
    (attempting open until success with a small backoff and max retries) so opening
    is robust across machines. Ensure you still log failures from the spawn/execFile
    and the URI open attempts.

In @src/session-status-hooks.ts:

  • Around line 216-228: readAllStatuses() now returns Map<string, StatusEntry>
    where StatusEntry = { status: SessionStatus, timestamp: number }, but
    electron-api.d.ts declares getSessionStatuses as returning Record<string, string
    | null>, causing callers like switcher-ui.tsx to use runtime guards; fix by
    either updating the IPC contract or adapting the handler: Option A — update
    electron-api.d.ts getSessionStatuses return type to something matching the
    actual data (e.g., Record<string, {status: SessionStatus, timestamp: number} |
    null>) so types align with readAllStatuses, or Option B — change the main IPC
    handler that calls readAllStatuses (the getSessionStatuses handler) to transform
    the Map into the declared Record<string, string | null> (map each sessionId to
    entry.status or null) before returning, and then update switcher-ui.tsx usages
    accordingly to rely on the chosen type; reference readAllStatuses,
    getSessionStatuses, electron-api.d.ts, switcher-ui.tsx and the main IPC handler
    when applying the change.

In @src/switcher-ui.tsx:

  • Around line 620-652: The setTimeout callback inside the
    onSessionStatusesUpdated listener captures a stale sessionSearchValue; fix by
    introducing a ref (e.g., sessionSearchValueRef) that is kept in sync with the
    search input onChange, then use sessionSearchValueRef.current instead of the
    outer sessionSearchValue inside the setTimeout callback when deciding whether to
    call filterSessionsLocally; update any places that read sessionSearchValue for
    filtering (notably the setSessions branch that calls filterSessionsLocally) to
    read from the ref so the latest search term is used.

Outside diff comments:
In @src/electron-api.d.ts:

  • Line 46: The type for getSessionStatuses is incorrect: update the declaration
    in electron-api.d.ts so it matches the actual return shape from main.ts (objects
    with status and timestamp) instead of string|null; e.g., replace the current
    Promise<Record<string, string | null>> return type with a record whose values
    are an object type containing status and timestamp (both nullable), or define a
    named interface (e.g., SessionStatus { status: string | null; timestamp: string
    | null }) and use Promise<Record<string, SessionStatus>> for getSessionStatuses
    to keep types consistent with the runtime value.

Nitpick comments:
In @docs/vscode-session-support-design.md:

  • Around line 55-57: The fenced code block containing the command open
    "vscode://anthropic.claude-code/open?session=" should include a language
    specifier for syntax highlighting; update the opening fence from tobash
    so the snippet is rendered as bash (e.g., change the block around the open
    "vscode://anthropic.claude-code/open?session=" line to use ```bash).

In @src/claude-session-utility.ts:

  • Around line 796-808: The try block that opens and reads the file can leak the
    file descriptor if an exception occurs between fs.openSync and fs.closeSync;
    modify the code around filePath so fd is declared outside (e.g. let fd), then
    open the file inside a try and always close it in a finally block (check fd is a
    valid number before calling fs.closeSync) while keeping the existing logic that
    builds buf, bytesRead, chunk and pushes into vsCodeFiles when the entrypoint
    matches; alternatively replace the open/read/close pattern with a single
    fs.readFileSync call to avoid manual FD management.

In @src/session-status-hooks.ts:

  • Around line 208-211: Rename the exported interface StatusEntry to IStatusEntry
    and update all usages accordingly; change the declaration export interface
    StatusEntry { ... } to export interface IStatusEntry { status: SessionStatus;
    timestamp: number; } and update any references in readAllStatuses,
    watchStatusDir and in main (previously importing/using StatusEntry) to use
    IStatusEntry so types and imports remain consistent.
  • Around line 190-206: readVSCodeIndex currently swallows file read and JSON
    parse errors (empty catch blocks) which hides corrupted lines; update
    readVSCodeIndex to log failures instead of silently ignoring them: when
    fs.readFileSync(VSCODE_INDEX_PATH) throws, catch and log the error (use the
    existing logger if available, e.g. processLogger.error, or console.error) and
    return the empty Map; when JSON.parse fails for a line, catch and log a
    debug-level message that includes the offending line content and the parse error
    (but continue processing other lines); keep the function behavior of returning a
    Map of valid sessionId→cwd entries.

In @src/switcher-ui.tsx:

  • Around line 396-493: The chained IPC promises (detectActiveSessions,
    detectTerminalApps, scanClosedVSCodeSessions, loadSessionEnrichment) lack error
    handling; add .catch() handlers to each promise chain to log the error via your
    logger (or console.error) and gracefully update state (e.g., avoid mutating
    allSessionsRef, call setAllSessions/setSessions with safe defaults or previous
    state, and avoid calling subsequent dependent operations like detectTerminalApps
    or loadSessionEnrichment when their prerequisite failed). Specifically update
    the promises returned by window.electronAPI.detectActiveSessions(),
    window.electronAPI.detectTerminalApps(...),
    window.electronAPI.scanClosedVSCodeSessions(activeIds) and
    window.electronAPI.loadSessionEnrichment(allVSCode) to attach .catch(...) that
    logs the error and performs minimal safe state updates (using setAllSessions,
    setSessions, setAssistantResponses, setTerminalApps as appropriate) so the UI
    remains consistent.

</details>

<details>
<summary>🪄 Autofix (Beta)</summary>

Fix all unresolved CodeRabbit comments on this PR:

- [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended)
- [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `95b94533-35ed-4eb2-8213-5c4a2fab6326`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 8dcadf7dc80a8379f9ddcf22aca2fd29291c3b32 and 5e4518776347bc23322413a63a9bba496b6ce942.

</details>

<details>
<summary>📒 Files selected for processing (10)</summary>

* `CHANGELOG.md`
* `docs/vscode-session-support-design.md`
* `package.json`
* `src/claude-session-utility.ts`
* `src/electron-api.d.ts`
* `src/main.ts`
* `src/popup.tsx`
* `src/preload.ts`
* `src/session-status-hooks.ts`
* `src/switcher-ui.tsx`

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread src/claude-session-utility.ts
Comment thread src/claude-session-utility.ts
Comment thread src/claude-session-utility.ts Outdated
Comment thread src/session-status-hooks.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.

1 issue found across 10 files

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:799">
P2: File descriptor leak if `fs.readSync` throws before `fs.closeSync`. Move `closeSync` into a `finally` block to ensure the descriptor is always released.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread src/claude-session-utility.ts
- File descriptor leak: wrap readSync in try/finally (cubic P2)
- Shell injection: use execFile instead of exec for projectPath
  (CodeRabbit #3)
- Stale sessionSearchValue: use ref in setTimeout closure
  (CodeRabbit #5)

Co-Authored-By: Claude Opus 4.6 (1M context) <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: 3

♻️ Duplicate comments (1)
src/switcher-ui.tsx (1)

425-428: ⚠️ Potential issue | 🟠 Major

Use the ref in the remaining background filter paths.

This is the same stale-search problem that was fixed in the timeout callback, but these branches, plus Line 388, still read sessionSearchValue. Because fetchClaudeSessions() is also called from mount-time listeners, a focus/tab refresh can repopulate sessions with the mount-time empty query while the search box still shows the current one.

Also applies to: 466-469

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/switcher-ui.tsx` around lines 425 - 428, The branches that update
sessions are reading the stale sessionSearchValue variable; change them to read
the stable ref (e.g. sessionSearchValueRef.current) instead of
sessionSearchValue wherever sessions are filtered in setSessions (the block
using updateActive and filterSessionsLocally) and the other background paths
called from fetchClaudeSessions() (also at the spots around the previously noted
lines ~388 and ~466-469). Specifically, inside the setSessions updater and any
mount-time or background callbacks that call filterSessionsLocally, replace
sessionSearchValue with sessionSearchValueRef.current so the active UI query is
always used when filtering sessions.
🤖 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/switcher-ui.tsx`:
- Around line 370-371: The derived search index (searchTarget built from
terminalBadge, terminalApps, customTitles, branches, prInfo, assistantResponses,
etc.) is updated asynchronously but the filtered sessions list (sessions derived
from allSessions + current query) isn’t re-run when those backing maps change;
add a small useEffect that re-applies the current query/filter whenever the
query or any of the async maps (terminalApps, customTitles, branches, prInfo,
assistantResponses) change, or change the sessions derivation to compute from
allSessions plus the latest map values so matches like "vscode", AI titles, or
PR URLs appear immediately without the user retyping.
- Around line 414-418: Merged VS Code sessions pushed from vscodeSessions (from
detectActiveSessions) are missing activity metadata, causing them to render as
closed and route to resume flows; when pushing into updated in the merge loop
(where entrypoint is set to 'claude-vscode'), include the activity fields—set
isActive: true and preserve activePid (e.g., activePid: vs.activePid ||
undefined) so rows render as active and trigger the instant-switch path
(affecting components that check isActive/activePid and calls like
openClaudeSession).
- Around line 349-351: Replace the loose `any`/`Record<string, any>` usage and
promise chains in the IPC-related logic with concrete types and async/await
error handling: define interfaces like `ISessionStatus` and `ISessionPreview`
and update `allSessionsRef`, `lastAssistantFetchRef`, and any IPC handler
signatures to use these types (e.g., `useRef<ISessionPreview[]>`,
`useRef<Record<string, ISessionStatus>>`), then refactor the IPC promise chains
(the blocks currently using `.then()` around the session/assistant fetches) into
`async` handlers that `await` each async call inside `try/catch` blocks and
surface/log errors instead of swallowing them; ensure each handler returns
properly typed data and update any downstream code to use the new types.

---

Duplicate comments:
In `@src/switcher-ui.tsx`:
- Around line 425-428: The branches that update sessions are reading the stale
sessionSearchValue variable; change them to read the stable ref (e.g.
sessionSearchValueRef.current) instead of sessionSearchValue wherever sessions
are filtered in setSessions (the block using updateActive and
filterSessionsLocally) and the other background paths called from
fetchClaudeSessions() (also at the spots around the previously noted lines ~388
and ~466-469). Specifically, inside the setSessions updater and any mount-time
or background callbacks that call filterSessionsLocally, replace
sessionSearchValue with sessionSearchValueRef.current so the active UI query is
always used when filtering sessions.
🪄 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: 86418e7f-1826-402d-bde1-848bad4bbeb6

📥 Commits

Reviewing files that changed from the base of the PR and between 5e45187 and 897c288.

📒 Files selected for processing (2)
  • src/claude-session-utility.ts
  • src/switcher-ui.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/claude-session-utility.ts

Comment thread src/switcher-ui.tsx
Comment thread src/switcher-ui.tsx
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.

2 issues found across 10 files

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/switcher-ui.tsx">

<violation number="1" location="src/switcher-ui.tsx:434">
P2: Re-filter the session list after terminal apps load; terminal-type searches stay stale until another keystroke or refetch.</violation>

<violation number="2" location="src/switcher-ui.tsx:468">
P2: Use the ref-backed query in this async update; otherwise late session-detection results can overwrite the user's current search with a stale filter.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread src/switcher-ui.tsx
Comment thread src/switcher-ui.tsx Outdated
Co-Authored-By: Claude Opus 4.6 (1M context) <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.

🧹 Nitpick comments (1)
src/switcher-ui.tsx (1)

397-496: Add error handling for chained IPC calls to prevent silent failures.

The nested .then() chains for detectActiveSessions, detectTerminalApps, scanClosedVSCodeSessions, and loadSessionEnrichment lack .catch() handlers. If any IPC call fails, the error is silently swallowed, which could leave the UI in an inconsistent state (e.g., sessions shown without activity status).

Consider adding error handling at minimum for the outer chain:

🛡️ Proposed fix to add error handling
     // Step 3: Detect active sessions in background (spawns processes)
     window.electronAPI.detectActiveSessions().then((result: any) => {
       // ... existing code ...
-    });
+    }).catch((err) => {
+      console.error('Failed to detect active sessions:', err);
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/switcher-ui.tsx` around lines 397 - 496, The promise chain starting at
window.electronAPI.detectActiveSessions() (and its nested calls
detectTerminalApps, scanClosedVSCodeSessions, loadSessionEnrichment) lacks error
handling; add .catch() handlers (or convert to async/await with try/catch)
around detectActiveSessions and each nested IPC call to log errors and apply
safe fallbacks so state setters (setAllSessions, setSessions, setTerminalApps,
setAssistantResponses, setCustomTitles, setBranches, setPrLinks) aren’t left
inconsistent; ensure each catch logs the error and either returns a sensible
default (empty arrays/objects) or skips subsequent dependent work (e.g., skip
detectTerminalApps if detectActiveSessions failed).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/switcher-ui.tsx`:
- Around line 397-496: The promise chain starting at
window.electronAPI.detectActiveSessions() (and its nested calls
detectTerminalApps, scanClosedVSCodeSessions, loadSessionEnrichment) lacks error
handling; add .catch() handlers (or convert to async/await with try/catch)
around detectActiveSessions and each nested IPC call to log errors and apply
safe fallbacks so state setters (setAllSessions, setSessions, setTerminalApps,
setAssistantResponses, setCustomTitles, setBranches, setPrLinks) aren’t left
inconsistent; ensure each catch logs the error and either returns a sensible
default (empty arrays/objects) or skips subsequent dependent work (e.g., skip
detectTerminalApps if detectActiveSessions failed).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8fbbdeef-74ca-49f2-870b-abf07c4e87e3

📥 Commits

Reviewing files that changed from the base of the PR and between 897c288 and 3b5153d.

📒 Files selected for processing (1)
  • src/switcher-ui.tsx

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.

1 participant