fix(diagnostics): count case-only worktree nesting on macOS (#17364)

This commit is contained in:
Neil
2026-08-30 01:23:43 -07:00
committed by GitHub
parent 3af2c665c0
commit d9cb020178
2 changed files with 61 additions and 9 deletions
@@ -59,23 +59,47 @@ describe('summarizeWorktreeNesting', () => {
})
it('normalizes windows separators, case, and trailing slashes', () => {
expect(summarizeWorktreeNesting(worktreesAtPaths('C:\\Repo\\', 'c:/repo/Nested'))).toEqual({
expect(
summarizeWorktreeNesting(worktreesAtPaths('C:\\Repo\\', 'c:/repo/Nested'), 'win32')
).toEqual({
maxDepth: 1,
nestedWorktrees: 1
})
})
it('preserves case-distinct POSIX and SSH worktree paths', () => {
it('preserves case-distinct remote POSIX worktree paths', () => {
expect(
summarizeWorktreeNesting(
worktreesAtPaths('/srv/Repo', '/srv/repo/nested').map((worktree) => ({
...worktree,
hostId: 'ssh:openclaw'
}))
})),
'darwin'
)
).toEqual({ maxDepth: 0, nestedWorktrees: 0 })
})
it('folds case-only local POSIX twins on macOS but not on Linux', () => {
const worktrees = worktreesAtPaths('/Users/ada/Repo', '/Users/ada/repo/nested')
expect(summarizeWorktreeNesting(worktrees, 'darwin')).toEqual({
maxDepth: 1,
nestedWorktrees: 1
})
expect(summarizeWorktreeNesting(worktrees, 'linux')).toEqual({
maxDepth: 0,
nestedWorktrees: 0
})
})
it('treats an absent hostId as the local host', () => {
expect(
summarizeWorktreeNesting(
[{ path: '/srv/repo' }, { path: '/srv/repo/nested', hostId: 'local' }],
'linux'
)
).toEqual({ maxDepth: 1, nestedWorktrees: 1 })
})
it('does not infer nesting across execution hosts', () => {
expect(
summarizeWorktreeNesting([
@@ -6,7 +6,12 @@
* Kept separate from the DOM/store wiring in diagnostic.ts so
* the arithmetic is unit-testable without an xterm or a live store.
*/
import { normalizeRuntimePathForComparison } from '../../../../shared/cross-platform-path'
import {
isWindowsAbsolutePathLike,
normalizeRuntimePathForComparison
} from '../../../../shared/cross-platform-path'
import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host'
import { getRendererAppPlatform } from '../renderer-app-platform'
export type LatencyPercentiles = {
count: number
@@ -55,17 +60,39 @@ export type WorktreeNestingCensus = {
type WorktreeLike = { path?: string | null; hostId?: string | null }
/**
* Case-only path twins are one directory on a case-insensitive filesystem, so the
* census must fold them or the nesting they form goes uncounted. Windows drive/UNC
* keys are already folded by `normalizeRuntimePathForComparison`; a POSIX key folds
* only for the local host, where the client platform really does report the
* filesystem (default macOS APFS is case-insensitive). Remote POSIX paths stay
* byte-exact — the execution host owns that fact and we cannot see it from here.
*/
function worktreeNestingKey(rawPath: string, foldPosixCase: boolean): string {
const normalized = normalizeRuntimePathForComparison(rawPath)
return foldPosixCase && !isWindowsAbsolutePathLike(rawPath.normalize('NFC'))
? normalized.toLowerCase()
: normalized
}
export function summarizeWorktreeNesting(
worktrees: readonly WorktreeLike[]
worktrees: readonly WorktreeLike[],
localPlatform: NodeJS.Platform = getRendererAppPlatform()
): WorktreeNestingCensus {
const pathsByHost = new Map<string | null, Set<string>>()
const pathsByHost = new Map<string, Set<string>>()
for (const worktree of worktrees) {
if (typeof worktree.path !== 'string' || worktree.path.length === 0) {
continue
}
const hostId = worktree.hostId ?? null
// An absent hostId means the local host, so it must not partition away from 'local'.
const hostId = worktree.hostId ?? LOCAL_EXECUTION_HOST_ID
const paths = pathsByHost.get(hostId) ?? new Set<string>()
paths.add(normalizeRuntimePathForComparison(worktree.path))
paths.add(
worktreeNestingKey(
worktree.path,
hostId === LOCAL_EXECUTION_HOST_ID && localPlatform === 'darwin'
)
)
pathsByHost.set(hostId, paths)
}
let maxDepth = 0
@@ -211,6 +238,7 @@ export function summarizeTypingScaleCensus(input: {
mountedAgentRowCount: number | null
storeListenerCount: number | null
focusedPane: FocusedPaneCensus | null
localPlatform?: NodeJS.Platform
}): TypingScaleCensus {
const state = input.state
const settings = state?.settings ?? null
@@ -221,7 +249,7 @@ export function summarizeTypingScaleCensus(input: {
appVersion: input.appVersion,
repos: state?.worktreesByRepo ? Object.keys(state.worktreesByRepo).length : 0,
worktrees: worktrees.length,
worktreeNesting: summarizeWorktreeNesting(worktrees),
worktreeNesting: summarizeWorktreeNesting(worktrees, input.localPlatform),
tabs: {
terminal: sumArrayLengths(state?.tabsByWorktree),
unified: sumArrayLengths(state?.unifiedTabsByWorktree)