Files
orca/src/shared/agent-scratch-worktrees.ts
Brennan Benson 1d2ba1e39d fix: never surface coding-agent scratch worktrees in the sidebar (#9535)
* fix: never surface coding-agent scratch worktrees in the sidebar

Sub-agent runs (e.g. Claude Code worktree isolation) create throwaway
git worktrees at tool-internal paths like <repo>/.claude/worktrees/.
These showed up as workspace rows for repos whose non-Orca worktree
visibility is 'show', and would otherwise ping the discovery card and
new-worktrees inbox on every fan-out.

Classify them as 'agent-scratch' via a curated path-segment matcher and
suppress them unconditionally: sidebar, discovery card, inbox, dialog
counts, add-handoff reveal trigger, and the metadata fallback. Selected
checkouts and explicitly imported paths still win.

Fixes #9388

* fix: preserve scratch worktree visibility boundaries

* fix: hide scratch worktrees from linked checkouts

* docs: explain scratch root normalization
2026-07-20 12:50:47 -07:00

40 lines
1.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)
}