Files
orca/config/scripts/worktree-storage/reclaim-dev-worktree-profiles.mjs
T
Neil 9bd6272fdf refactor(tooling): organize build, CI, and test owners (#38130)
* refactor(tooling): organize test runtime and maintenance owners

* test(tooling): preserve relocated runtime fixture dependency edges

* refactor(tooling): preserve xterm routing owner checks

* ci(tooling): include routing owner in sparse checkout

* test(tooling): preserve copied workflow dependency closures

* fix(tooling): normalize hashed JSON assets in release parity

* fix(tooling): distinguish asset aliases from literal references

* fix(tooling): frame normalized asset reference payloads

* fix(tooling): normalize decoded JSON asset references

* refactor(tooling): organize build, CI and release owners

* fix(tooling): align moved generator and workflow path contracts

* fix(tooling): update generated plugin bundle owner banners

* test(tooling): create relocated mobile verifier fixture directories

* test(tooling): copy headless detector dependency closure

* test(tooling): align native batch policy fixtures with owner path

* fix(tooling): link moved reliability command to historical evidence

* fix(tooling): preserve nested sandbox owners and canonical guide source

* fix(tooling): target release version owners in checkout fixture
2026-11-04 21:08:19 -08:00

137 lines
4.3 KiB
JavaScript

#!/usr/bin/env node
// Removes per-worktree `pnpm dev` profiles (see dev-instance-isolation.mjs) whose worktree is gone.
import { lstat, readdir, readFile, readlink, realpath, rm } from 'node:fs/promises'
import path from 'node:path'
import {
getWorktreeDevProfilesRoot,
readWorktreeProfileMarker,
sameFilesystemPath,
WORKTREE_PROFILE_NAME_PATTERN
} from '../development/instance-isolation/dev-instance-isolation.mjs'
import { localRepositoryWorktrees } from './local-repository-worktrees.mjs'
import { parseReclaimArguments } from './reclaim-worktree-command.mjs'
import {
isProcessAlive,
readDaemonPids
} from '../development/remote-session-cleanup/remote-agent-session-process-cleanup.mjs'
async function exists(filePath) {
try {
await lstat(filePath)
return true
} catch (error) {
if (error.code === 'ENOENT') {
return false
}
throw error
}
}
async function canonical(filePath) {
try {
return await realpath(filePath)
} catch {
return path.resolve(filePath)
}
}
/** PIDs that may still hold the profile: the app (Chromium's lock), its runtime, and terminal daemons. */
async function profileOwnerPids(userDataPath) {
const pids = [...readDaemonPids(userDataPath)]
try {
// POSIX Chromium writes SingletonLock as a symlink to `<host>-<pid>`.
const match = /-(\d+)$/.exec(await readlink(path.join(userDataPath, 'SingletonLock')))
if (match) {
pids.push(Number(match[1]))
}
} catch {}
try {
const runtime = JSON.parse(await readFile(path.join(userDataPath, 'orca-runtime.json'), 'utf8'))
if (Number.isInteger(runtime?.pid)) {
pids.push(runtime.pid)
}
} catch {}
return pids
}
export async function reclaimDevWorktreeProfiles({
repoRoot = process.cwd(),
apply = false,
excludeWorktree,
profilesRoot = getWorktreeDevProfilesRoot(),
repository,
isAlive = isProcessAlive,
report = console.log
} = {}) {
const { commonDirectory, worktrees } = repository ?? (await localRepositoryWorktrees(repoRoot))
const liveWorktrees = await Promise.all(worktrees.map(canonical))
const result = { entries: 0, protected: 0 }
let names
try {
names = await readdir(profilesRoot)
} catch (error) {
if (error.code === 'ENOENT') {
return result
}
throw error
}
for (const name of names) {
const profileDir = path.join(profilesRoot, name)
if (!WORKTREE_PROFILE_NAME_PATTERN.test(name) || !(await lstat(profileDir)).isDirectory()) {
continue
}
const marker = readWorktreeProfileMarker(profileDir)
// Profiles from other repositories belong to their own repository's cleanup.
if (
!marker?.gitCommonDir ||
!sameFilesystemPath(await canonical(marker.gitCommonDir), commonDirectory)
) {
continue
}
if (
(excludeWorktree && sameFilesystemPath(marker.repoRoot, excludeWorktree)) ||
liveWorktrees.some((worktree) => sameFilesystemPath(worktree, marker.repoRoot)) ||
(await exists(marker.repoRoot))
) {
continue
}
if ((await profileOwnerPids(profileDir)).some((pid) => isAlive(pid))) {
result.protected += 1
report(`retained dev profile ${JSON.stringify(profileDir)} (a process still uses it)`)
continue
}
if (apply) {
try {
await rm(profileDir, { recursive: true, force: true })
} catch (error) {
// Windows keeps a running profile's lock files open, so removal fails instead of racing.
result.protected += 1
report(`retained dev profile ${JSON.stringify(profileDir)} (${error.message})`)
continue
}
}
result.entries += 1
report(
`${apply ? 'removed' : 'would remove'} dev profile ${JSON.stringify(profileDir)} for removed worktree ${JSON.stringify(marker.repoRoot)}`
)
}
report(
`${apply ? 'Removed' : 'Would remove'} ${result.entries} dev profile(s) of removed worktrees; retained ${result.protected} in use.`
)
return result
}
if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(import.meta.filename)) {
const options = parseReclaimArguments(process.argv.slice(2))
if (options.help) {
console.log(
'Usage: node config/scripts/worktree-storage/reclaim-dev-worktree-profiles.mjs [--apply] [--repo DIRECTORY]'
)
} else {
reclaimDevWorktreeProfiles(options).catch((error) => {
console.error(error.message)
process.exitCode = 1
})
}
}