Files
orca/src/shared/agent-scratch-worktrees.ts
T
Brennan Benson 4062ed5a73 perf(worktrees): back off worktree scans for agent-scratch repos (#9985)
Production crash diagnostics measured ~128 `git worktree list` execs/min
(9,400 in one 80-minute session, ~16% of wall-clock in git subprocesses):
the resolved-worktree scan fans out over every registered repo on a 30s
cache TTL, and most registered repos on the affected installs were
agent-CLI scratch repos (~/.codex-tmp capsules, vendor imports, skill
checkouts) that need no freshness.

Classify agent-scratch repo roots with a curated shared matcher and stamp
their scan-cache entries with a 5-minute TTL instead of 30s. Orca-driven
mutations still bypass the TTL via the per-repo generation bump, so only
passive pickup of external changes slows for scratch repos. Expected
steady-state reduction on the measured install: ~82% fewer git spawns.
2026-07-22 14:42:58 -07:00

64 lines
2.5 KiB
TypeScript

import { normalizeRuntimePathForComparison } from './cross-platform-path'
/** Why: agent CLIs reserve these repo-root paths for scratch; broader matches
* can hide legitimate user worktrees (#9388). */
const AGENT_SCRATCH_PATH_PREFIXES: readonly (readonly string[])[] = [
['.claude', 'worktrees'],
['.gsd-workspaces']
]
export type AgentScratchWorktreePathMatcher = (worktreePath: string) => boolean
export function createAgentScratchWorktreePathMatcher(
checkoutPaths: readonly string[]
): AgentScratchWorktreePathMatcher {
const checkoutPathKeys = new Set(checkoutPaths.map(normalizeRuntimePathForComparison))
return (worktreePath) => {
const segments = normalizeRuntimePathForComparison(worktreePath).split('/')
for (const prefix of AGENT_SCRATCH_PATH_PREFIXES) {
for (let index = 0; index + prefix.length < segments.length; index += 1) {
if (!prefix.every((segment, offset) => segments[index + offset] === segment)) {
continue
}
const checkoutPath = segments.slice(0, index).join('/')
// Why: splitting strips the separator from filesystem roots, but normalized checkout keys retain it.
const checkoutPathKey = /^[a-z]:$/i.test(checkoutPath)
? `${checkoutPath}/`
: checkoutPath || '/'
if (checkoutPathKeys.has(checkoutPathKey)) {
return true
}
}
}
return false
}
}
export function isAgentScratchWorktreePath(repoPath: string, worktreePath: string): boolean {
return createAgentScratchWorktreePathMatcher([repoPath])(worktreePath)
}
/** Why: agent CLIs also mint whole scratch *repos* under these containers; a
* repo registered at such a root is agent-internal, not a user project (#9388). */
const AGENT_SCRATCH_REPO_ROOT_SEGMENTS: readonly (readonly string[])[] = [
['.codex-tmp'],
['.codex', 'vendor_imports'],
['.claude', 'skills'],
...AGENT_SCRATCH_PATH_PREFIXES
]
export function isAgentScratchRepoRootPath(repoPath: string): boolean {
const segments = normalizeRuntimePathForComparison(repoPath).split('/')
for (const marker of AGENT_SCRATCH_REPO_ROOT_SEGMENTS) {
// Why: match the marker anywhere above the repo root (the repo lives at or
// under the scratch container), unlike worktree matching which anchors to a
// registered checkout path.
for (let index = 0; index + marker.length <= segments.length; index += 1) {
if (marker.every((segment, offset) => segments[index + offset] === segment)) {
return true
}
}
}
return false
}