chore(workspaces): drop the dead workspaceCleanup:hasKillableLocalProcesses IPC (#18386)

This commit is contained in:
Neil
2026-09-03 02:00:46 -07:00
committed by GitHub
parent b8da193b7a
commit a9f2fbb684
9 changed files with 8 additions and 268 deletions
@@ -1,112 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { ipcMain } from 'electron'
import type { Store } from '../persistence'
const { getSshPtyProviderMock } = vi.hoisted(() => ({
getSshPtyProviderMock: vi.fn()
}))
vi.mock('electron', () => ({
ipcMain: {
handle: vi.fn(),
removeHandler: vi.fn()
}
}))
vi.mock('./pty', () => ({
getSshPtyProvider: getSshPtyProviderMock
}))
vi.mock('../memory/pty-registry', () => ({
listRegisteredPtys: vi.fn(() => [])
}))
vi.mock('../workspace-cleanup-scan-snapshot', () => ({
persistWorkspaceCleanupScanResult: vi.fn(async () => undefined),
readWorkspaceCleanupScanSnapshot: vi.fn(async () => null)
}))
vi.mock('../workspace-cleanup-removal-snapshot-prune', () => ({
beginWorkspaceCleanupRemovalSnapshotPruneBatch: vi.fn(),
finishWorkspaceCleanupRemovalSnapshotPruneBatch: vi.fn(async () => undefined),
recordWorkspaceCleanupRemovalSnapshotPrune: vi.fn()
}))
import { registerWorkspaceCleanupHandlers } from './workspace-cleanup'
function makeEmptyStore(): Store {
return {
getProfileStorageDirectory: () => '/profile-a',
getRepos: () => [],
getWorktreeMeta: () => ({}),
getAllWorktreeMeta: () => ({}),
getGitHubCache: () => ({ pr: {}, issue: {} })
} as unknown as Store
}
function getPreflightHandler(): ((...args: never[]) => unknown) | undefined {
return vi
.mocked(ipcMain.handle)
.mock.calls.find(([channel]) => channel === 'workspaceCleanup:hasKillableLocalProcesses')?.[1]
}
describe('workspace cleanup process preflight', () => {
beforeEach(() => {
vi.mocked(ipcMain.handle).mockReset()
getSshPtyProviderMock.mockReset()
})
it('reports local processes that workspace deletion would kill', async () => {
const localProvider = {
listProcesses: vi.fn().mockResolvedValue([
{
id: 'repo-1::/repo-feature@@session-1',
cwd: '/repo-feature',
title: 'zsh'
}
])
}
registerWorkspaceCleanupHandlers(makeEmptyStore(), {
runtime: {
hasTerminalsForWorktree: vi.fn().mockResolvedValue(false)
} as never,
getLocalPtyProvider: () => localProvider as never
})
await expect(
getPreflightHandler()?.({} as never, { worktreeId: 'repo-1::/repo-feature' } as never)
).resolves.toEqual({
hasKillableProcesses: true
})
})
it('reports SSH processes inside the remote workspace path', async () => {
getSshPtyProviderMock.mockReturnValue({
listProcesses: vi.fn().mockResolvedValue([
{
id: 'remote-session-1',
cwd: '/remote/repo-feature/subdir',
title: 'codex'
}
])
})
registerWorkspaceCleanupHandlers(makeEmptyStore(), {
runtime: {
hasTerminalsForWorktree: vi.fn().mockResolvedValue(false)
} as never
})
await expect(
getPreflightHandler()?.(
{} as never,
{
worktreeId: 'repo-ssh::/remote/repo-feature',
connectionId: 'ssh-1',
worktreePath: '/remote/repo-feature'
} as never
)
).resolves.toEqual({
hasKillableProcesses: true
})
})
})
+1 -115
View File
@@ -1,14 +1,8 @@
import { ipcMain } from 'electron'
import type { Store } from '../persistence'
import type { IPtyProvider } from '../providers/types'
import type { OrcaRuntimeService } from '../runtime/orca-runtime'
import { listRegisteredPtys } from '../memory/pty-registry'
import { getSshPtyProvider } from './pty'
import {
WORKSPACE_CLEANUP_CLASSIFIER_VERSION,
type WorkspaceCleanupDismissArgs,
type WorkspaceCleanupLocalProcessArgs,
type WorkspaceCleanupLocalProcessResult,
type WorkspaceCleanupScanArgs,
type WorkspaceCleanupScanResult,
type WorkspaceCleanupSnapshotPruneBatchArgs,
@@ -30,11 +24,6 @@ import {
export { scanWorkspaceCleanup }
type WorkspaceCleanupHandlerDeps = {
runtime?: OrcaRuntimeService
getLocalPtyProvider?: () => IPtyProvider
}
// Why: module scope — handler re-registration on a new main window must not
// orphan the previous window's controllers in a discarded map.
const activeScans = new Map<string, AbortController>()
@@ -47,17 +36,13 @@ function getBroadScanModeKey(senderId: number, args: WorkspaceCleanupScanArgs):
return `${senderId}\0${args.includeAllWorkspaces === true}`
}
export function registerWorkspaceCleanupHandlers(
store: Store,
deps: WorkspaceCleanupHandlerDeps = {}
): void {
export function registerWorkspaceCleanupHandlers(store: Store): void {
const snapshotDirectory = store.getProfileStorageDirectory()
ipcMain.removeHandler('workspaceCleanup:scan')
ipcMain.removeHandler('workspaceCleanup:cancelScan')
ipcMain.removeHandler('workspaceCleanup:getCachedScan')
ipcMain.removeHandler('workspaceCleanup:dismiss')
ipcMain.removeHandler('workspaceCleanup:clearDismissals')
ipcMain.removeHandler('workspaceCleanup:hasKillableLocalProcesses')
ipcMain.removeHandler('workspaceCleanup:beginRemovalSnapshotPruneBatch')
ipcMain.removeHandler('workspaceCleanup:recordRemovalSnapshotPrune')
ipcMain.removeHandler('workspaceCleanup:finishRemovalSnapshotPruneBatch')
@@ -161,16 +146,6 @@ export function registerWorkspaceCleanupHandlers(
store.updateUI({ workspaceCleanup: { dismissals: {} } })
})
ipcMain.handle(
'workspaceCleanup:hasKillableLocalProcesses',
async (
_event,
args: WorkspaceCleanupLocalProcessArgs
): Promise<WorkspaceCleanupLocalProcessResult> => ({
hasKillableProcesses: await hasKillableProcesses(args, deps)
})
)
ipcMain.handle(
'workspaceCleanup:beginRemovalSnapshotPruneBatch',
(_event, args: WorkspaceCleanupSnapshotPruneBatchArgs) => {
@@ -214,92 +189,3 @@ function getWorkspaceCleanupScanKey(senderId: number, scanId: unknown): string |
? `${senderId}\0${scanId}`
: null
}
async function hasKillableProcesses(
args: WorkspaceCleanupLocalProcessArgs,
deps: WorkspaceCleanupHandlerDeps
): Promise<boolean | null> {
const { worktreeId } = args
if (typeof worktreeId !== 'string' || worktreeId.length === 0) {
return false
}
let livenessUnknown = false
if (deps.runtime) {
try {
if (await deps.runtime.hasTerminalsForWorktree(worktreeId)) {
return true
}
} catch {
livenessUnknown = true
}
}
if (args.connectionId) {
return hasKillableSshProcesses(args.connectionId, args.worktreePath ?? '', livenessUnknown)
}
const registryPtyIds = new Set(
listRegisteredPtys()
.filter((entry) => entry.worktreeId === worktreeId)
.map((entry) => entry.ptyId)
)
const provider = deps.getLocalPtyProvider?.()
if (!provider) {
return registryPtyIds.size > 0 ? true : null
}
try {
const prefix = `${worktreeId}@@`
const sessions = await provider.listProcesses()
if (
sessions.some((session) => session.id.startsWith(prefix) || registryPtyIds.has(session.id))
) {
return true
}
return livenessUnknown ? null : false
} catch {
return registryPtyIds.size > 0 ? true : null
}
}
async function hasKillableSshProcesses(
connectionId: string,
worktreePath: string,
livenessUnknown: boolean
): Promise<boolean | null> {
const provider = getSshPtyProvider(connectionId)
if (!provider) {
return null
}
try {
const normalizedWorktreePath = normalizeRemotePath(worktreePath)
const sessions = await provider.listProcesses()
if (
sessions.some((session) => {
if (session.id.startsWith(`${worktreePath}@@`)) {
return true
}
return (
normalizedWorktreePath.length > 0 &&
isPathWithin(normalizeRemotePath(session.cwd), normalizedWorktreePath)
)
})
) {
return true
}
return livenessUnknown ? null : false
} catch {
return null
}
}
function normalizeRemotePath(path: string): string {
return path.replace(/\\/g, '/').replace(/\/+$/, '')
}
function isPathWithin(candidatePath: string, parentPath: string): boolean {
return candidatePath === parentPath || candidatePath.startsWith(`${parentPath}/`)
}
@@ -13,7 +13,6 @@ import { setWorktreeCatalogRemoteClientNotifier } from '../ipc/watched-worktree-
import { registerWorktreeHandlers } from '../ipc/worktrees'
import { registerWorkspaceCleanupHandlers } from '../ipc/workspace-cleanup'
import {
getLocalPtyProvider,
registerPtyHandlers,
type CodexHomePtySpawnedLifecycleArgs,
type GetSelectedCodexHomePath,
@@ -83,7 +82,7 @@ export function attachMainWindowServices(
// Why: folder projects get no watch target, so an external `git init` needs its own
// marker poll to upgrade them without a restart (#11477).
startFolderRepoGitUpgradeWatch(store, mainWindow)
registerWorkspaceCleanupHandlers(store, { runtime, getLocalPtyProvider })
registerWorkspaceCleanupHandlers(store)
registerPtyHandlers(
mainWindow,
runtime,
-5
View File
@@ -1,7 +1,5 @@
import type {
WorkspaceCleanupDismissArgs,
WorkspaceCleanupLocalProcessArgs,
WorkspaceCleanupLocalProcessResult,
WorkspaceCleanupScanArgs,
WorkspaceCleanupScanProgress,
WorkspaceCleanupScanResult,
@@ -24,9 +22,6 @@ export type WorkspaceCleanupApi = {
getCachedScan: () => Promise<WorkspaceCleanupScanResult | null>
dismiss: (args: WorkspaceCleanupDismissArgs) => Promise<void>
clearDismissals: () => Promise<void>
hasKillableLocalProcesses: (
args: WorkspaceCleanupLocalProcessArgs
) => Promise<WorkspaceCleanupLocalProcessResult>
beginRemovalSnapshotPruneBatch?: (args: WorkspaceCleanupSnapshotPruneBatchArgs) => Promise<void>
recordRemovalSnapshotPrune?: (args: WorkspaceCleanupSnapshotPruneRecordArgs) => Promise<void>
finishRemovalSnapshotPruneBatch?: (args: WorkspaceCleanupSnapshotPruneBatchArgs) => Promise<void>
@@ -25,8 +25,6 @@ export const workspaceCleanupApi = {
getCachedScan: () => ipcRenderer.invoke('workspaceCleanup:getCachedScan'),
dismiss: (args) => ipcRenderer.invoke('workspaceCleanup:dismiss', args),
clearDismissals: () => ipcRenderer.invoke('workspaceCleanup:clearDismissals'),
hasKillableLocalProcesses: (args) =>
ipcRenderer.invoke('workspaceCleanup:hasKillableLocalProcesses', args),
beginRemovalSnapshotPruneBatch: (args) =>
ipcRenderer.invoke('workspaceCleanup:beginRemovalSnapshotPruneBatch', args),
recordRemovalSnapshotPrune: (args) =>
@@ -120,8 +120,7 @@ function installApi(cachedScan: WorkspaceCleanupScanResult | null): ScanRig {
scan: rig.scan,
getCachedScan: vi.fn().mockResolvedValue(cachedScan),
dismiss: vi.fn().mockResolvedValue(undefined),
clearDismissals: vi.fn().mockResolvedValue(undefined),
hasKillableLocalProcesses: vi.fn().mockResolvedValue({ hasKillableProcesses: false })
clearDismissals: vi.fn().mockResolvedValue(undefined)
},
workspaceSpace: {
getCachedAnalysis: vi.fn().mockResolvedValue(null),
@@ -258,10 +258,7 @@ describe('workspace cleanup removal and protection', () => {
})
),
dismiss: vi.fn().mockResolvedValue(undefined),
clearDismissals: vi.fn().mockResolvedValue(undefined),
hasKillableLocalProcesses: vi.fn().mockResolvedValue({
hasKillableProcesses: false
})
clearDismissals: vi.fn().mockResolvedValue(undefined)
}
}
}
@@ -291,10 +288,7 @@ describe('workspace cleanup removal and protection', () => {
workspaceCleanup: {
scan,
dismiss: vi.fn().mockResolvedValue(undefined),
clearDismissals: vi.fn().mockResolvedValue(undefined),
hasKillableLocalProcesses: vi.fn().mockResolvedValue({
hasKillableProcesses: false
})
clearDismissals: vi.fn().mockResolvedValue(undefined)
}
}
}
@@ -333,10 +327,7 @@ describe('workspace cleanup removal and protection', () => {
workspaceCleanup: {
scan,
dismiss: vi.fn().mockResolvedValue(undefined),
clearDismissals: vi.fn().mockResolvedValue(undefined),
hasKillableLocalProcesses: vi.fn().mockResolvedValue({
hasKillableProcesses: false
})
clearDismissals: vi.fn().mockResolvedValue(undefined)
}
}
}
@@ -368,10 +359,7 @@ describe('workspace cleanup removal and protection', () => {
workspaceCleanup: {
scan,
dismiss: vi.fn().mockResolvedValue(undefined),
clearDismissals: vi.fn().mockResolvedValue(undefined),
hasKillableLocalProcesses: vi.fn().mockResolvedValue({
hasKillableProcesses: true
})
clearDismissals: vi.fn().mockResolvedValue(undefined)
}
}
}
@@ -93,10 +93,7 @@ export function installWorkspaceCleanupApi(
scan,
getCachedScan,
dismiss: vi.fn().mockResolvedValue(undefined),
clearDismissals: vi.fn().mockResolvedValue(undefined),
hasKillableLocalProcesses: vi.fn().mockResolvedValue({
hasKillableProcesses: false
})
clearDismissals: vi.fn().mockResolvedValue(undefined)
}
}
}
-10
View File
@@ -100,12 +100,6 @@ export type WorkspaceCleanupScanArgs = {
export const WORKSPACE_CLEANUP_TARGET_BATCH_LIMIT = 500
export type WorkspaceCleanupLocalProcessArgs = {
worktreeId: string
connectionId?: string | null
worktreePath?: string
}
export type WorkspaceCleanupSnapshotPruneBatchArgs = {
batchId: string
}
@@ -144,10 +138,6 @@ export type WorkspaceCleanupUnverifiedRemovalConsent = {
attemptId: string
}
export type WorkspaceCleanupLocalProcessResult = {
hasKillableProcesses: boolean | null
}
export type WorkspaceCleanupDismissArgs = {
dismissals: WorkspaceCleanupDismissal[]
/** Removed worktrees' persisted dismissals are dead weight; prune them. */