fix: deduplicate idle sessions and refresh session data on transitions

Multiple claude processes in the same CWD share one sessionFile via
findActiveJsonl, causing duplicate idle entries. Now deduplicates by
project+sessionFile key. Also refreshes session data on busy→idle
transitions so the parser fix takes effect immediately.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-02-24 01:54:15 +00:00
parent 66ac50813a
commit 6e9788804f
2 changed files with 10 additions and 6 deletions

View File

@@ -267,14 +267,17 @@ export interface IdleSessionInfo {
} }
export function getIdleSessions(projects: Project[]): IdleSessionInfo[] { export function getIdleSessions(projects: Project[]): IdleSessionInfo[] {
const idle: IdleSessionInfo[] = [] // Deduplicate by project+sessionFile — multiple processes in same CWD share one sessionFile
const seen = new Map<string, IdleSessionInfo>()
for (const project of projects) { for (const project of projects) {
const sessions = sessionsByPath.get(project.path) const sessions = sessionsByPath.get(project.path)
if (!sessions) continue if (!sessions) continue
for (const s of sessions) { for (const s of sessions) {
if (s.busy) continue if (s.busy) continue
if (!s.lastActivityMs) continue if (!s.lastActivityMs) continue
// Find matching session info for title/prompt const dedupeKey = `${project.path}:${s.sessionFile || s.pid}`
const existing = seen.get(dedupeKey)
if (existing && existing.idleSinceMs >= s.lastActivityMs) continue
let title = "" let title = ""
let lastPrompt = "" let lastPrompt = ""
let lastResponse = "" let lastResponse = ""
@@ -288,7 +291,7 @@ export function getIdleSessions(projects: Project[]): IdleSessionInfo[] {
lastResponse = match.lastAssistantMsg lastResponse = match.lastAssistantMsg
} }
} }
idle.push({ seen.set(dedupeKey, {
projectPath: project.path, projectPath: project.path,
projectName: project.name, projectName: project.name,
tty: s.tty, tty: s.tty,
@@ -299,6 +302,7 @@ export function getIdleSessions(projects: Project[]): IdleSessionInfo[] {
}) })
} }
} }
const idle = Array.from(seen.values())
idle.sort((a, b) => b.idleSinceMs - a.idleSinceMs) idle.sort((a, b) => b.idleSinceMs - a.idleSinceMs)
return idle return idle
} }

View File

@@ -812,14 +812,14 @@ async function main() {
} else { } else {
const sessions = await detectActiveSessions() const sessions = await detectActiveSessions()
const changed = updateProjectSessions(projects, sessions) const changed = updateProjectSessions(projects, sessions)
// Eagerly load session data for active projects (needed for idle panel) const transitioned = checkTransitions(projects, prevBusySnapshot)
// Eagerly load/refresh session data for active projects (needed for idle panel)
for (const p of projects) { for (const p of projects) {
if (p.activeSessions > 0 && !p.sessions) { if (p.activeSessions > 0 && (!p.sessions || transitioned.length > 0)) {
p.sessions = await loadSessions(p.path) p.sessions = await loadSessions(p.path)
p.sessionCount = p.sessions.length p.sessionCount = p.sessions.length
} }
} }
const transitioned = checkTransitions(projects, prevBusySnapshot)
prevBusySnapshot = snapshotBusy(projects) prevBusySnapshot = snapshotBusy(projects)
if (transitioned.length > 0) { if (transitioned.length > 0) {
playDoneSound() playDoneSound()