π File detail
utils/computerUse/cleanup.ts
π― Use case
This file lives under βutils/β, which covers cross-cutting helpers (shell, tempfiles, settings, messages, process input, β¦). On the API surface it exposes cleanupComputerUseAfterTurn β mainly functions, hooks, or classes. It composes internal code from Tool, debug, errors, withResolvers, and computerUseLock (relative imports). What the file header says: cu.apps.unhide is NOT one of the four @MainActor methods wrapped by drainRunLoop's 30s backstop. On abort paths (where the user hit Ctrl+C because something was slow) a hang here would wedge the abort. Generous timeout β unhide should be ~instant; if it takes 5s something is wron.
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
cu.apps.unhide is NOT one of the four @MainActor methods wrapped by drainRunLoop's 30s backstop. On abort paths (where the user hit Ctrl+C because something was slow) a hang here would wedge the abort. Generous timeout β unhide should be ~instant; if it takes 5s something is wrong
π€ Exports (heuristic)
cleanupComputerUseAfterTurn
π₯οΈ Source preview
import type { ToolUseContext } from '../../Tool.js'
import { logForDebugging } from '../debug.js'
import { errorMessage } from '../errors.js'
import { withResolvers } from '../withResolvers.js'
import { isLockHeldLocally, releaseComputerUseLock } from './computerUseLock.js'
import { unregisterEscHotkey } from './escHotkey.js'
// cu.apps.unhide is NOT one of the four @MainActor methods wrapped by
// drainRunLoop's 30s backstop. On abort paths (where the user hit Ctrl+C
// because something was slow) a hang here would wedge the abort. Generous
// timeout β unhide should be ~instant; if it takes 5s something is wrong
// and proceeding is better than waiting. The Swift call continues in the
// background regardless; we just stop blocking on it.
const UNHIDE_TIMEOUT_MS = 5000
/**
* Turn-end cleanup for the chicago MCP surface: auto-unhide apps that
* `prepareForAction` hid, then release the file-based lock.
*
* Called from three sites: natural turn end (`stopHooks.ts`), abort during
* streaming (`query.ts` aborted_streaming), abort during tool execution
* (`query.ts` aborted_tools). All three reach this via dynamic import gated
* on `feature('CHICAGO_MCP')`. `executor.js` (which pulls both native
* modules) is dynamic-imported below so non-CU turns don't load native
* modules just to no-op.
*
* No-ops cheaply on non-CU turns: both gate checks are zero-syscall.
*/
export async function cleanupComputerUseAfterTurn(
ctx: Pick<
ToolUseContext,
'getAppState' | 'setAppState' | 'sendOSNotification'
>,
): Promise<void> {
const appState = ctx.getAppState()
const hidden = appState.computerUseMcpState?.hiddenDuringTurn
if (hidden && hidden.size > 0) {
const { unhideComputerUseApps } = await import('./executor.js')
const unhide = unhideComputerUseApps([...hidden]).catch(err =>
logForDebugging(
`[Computer Use MCP] auto-unhide failed: ${errorMessage(err)}`,
),
)
const timeout = withResolvers<void>()
const timer = setTimeout(timeout.resolve, UNHIDE_TIMEOUT_MS)
await Promise.race([unhide, timeout.promise]).finally(() =>
clearTimeout(timer),
)
ctx.setAppState(prev =>
prev.computerUseMcpState?.hiddenDuringTurn === undefined
? prev
: {
...prev,
computerUseMcpState: {
...prev.computerUseMcpState,
hiddenDuringTurn: undefined,
},
},
)
}
// Zero-syscall pre-check so non-CU turns don't touch disk. Release is still
// idempotent (returns false if already released or owned by another session).
if (!isLockHeldLocally()) return
// Unregister before lock release so the pump-retain drops as soon as the
// CU session ends. Idempotent β no-ops if registration failed at acquire.
// Swallow throws so a NAPI unregister error never prevents lock release β
// a held lock blocks the next CU session with "in use by another session".
try {
unregisterEscHotkey()
} catch (err) {
logForDebugging(
`[Computer Use MCP] unregisterEscHotkey failed: ${errorMessage(err)}`,
)
}
if (await releaseComputerUseLock()) {
ctx.sendOSNotification?.({
message: 'Claude is done using your computer',
notificationType: 'computer_use_exit',
})
}
}