From 382dfa427bacbaddd10f28bea76e68dca3aceead Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 28 Aug 2026 08:38:49 -0700 Subject: [PATCH] fix(cleanup): read the host's process evidence, not its legacy collapse A host that answered but whose probes failed publishes the deliberate legacy collapse (foregroundProcess = the stable-cache shell name, hasChildProcesses = false) with the real verdict in processEvidence. That legacy half is byte-identical to an idle shell, so cleanup dropped the blocker and select-all swept the row. unavailable only ever covered a handle we could not route to. Old hosts keep their old meaning: readPtyProcessInspectionEvidence falls back to the legacy interpretation when the field is absent. --- .../workspace-cleanup-local-evidence.ts | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/store/slices/workspace-cleanup-local-evidence.ts b/src/renderer/src/store/slices/workspace-cleanup-local-evidence.ts index bea296d04b5..083ab317022 100644 --- a/src/renderer/src/store/slices/workspace-cleanup-local-evidence.ts +++ b/src/renderer/src/store/slices/workspace-cleanup-local-evidence.ts @@ -7,6 +7,7 @@ import { classifyTitleActivity, isExplicitAgentStatusFresh } from '@/lib/pane-ag import type { WorkspaceCleanupCandidate } from '../../../../shared/workspace-cleanup' import { getWorktreeVisitTimestamp } from '@/lib/worktree-visit-recency' import { inspectRuntimeTerminalProcess } from '@/runtime/runtime-terminal-process-inspection' +import { readPtyProcessInspectionEvidence } from '../../../../shared/pty-process-inspection-evidence' const RECENT_VISIBLE_CONTEXT_MS = 24 * 60 * 60 * 1000 const VIEWED_FROM_CLEANUP_MS = 2 * 60 * 60 * 1000 @@ -179,8 +180,24 @@ export async function probeTerminalLiveness( unverifiable = true continue } - const processName = normalizeProcessName(inspection.foregroundProcess) - if (!inspection.hasChildProcesses && (!processName || SHELL_PROCESS_NAMES.has(processName))) { + // Why the evidence and not the legacy fields: `unavailable` only covers a + // handle we could not route to. A host that DID answer but whose probes + // failed publishes the legacy collapse (null/false, or the shell name from + // the stable cache) — byte-identical to an idle shell. Only processEvidence + // separates "observed idle" from "could not ask", and this is a delete path. + const evidence = readPtyProcessInspectionEvidence(inspection) + if ( + evidence.foreground.verdict === 'unverifiable' || + evidence.children.verdict === 'unverifiable' + ) { + unverifiable = true + continue + } + const processName = normalizeProcessName(evidence.foreground.processName) + if ( + evidence.children.verdict !== 'live' && + (!processName || SHELL_PROCESS_NAMES.has(processName)) + ) { continue } if (