perf: bound branch rename dedupe cache (#4115)

This commit is contained in:
Neil
2026-05-31 04:46:49 -07:00
committed by GitHub
parent e6c55d76e6
commit f37258a21d
2 changed files with 52 additions and 1 deletions
@@ -38,6 +38,7 @@ vi.mock('../text-generation/commit-message-agent-environment', () => ({
vi.mock('../ipc/worktree-logic', () => ({ computeBranchName: computeBranchNameMock }))
import {
FIRST_WORK_BRANCH_RENAME_SETTLED_CACHE_LIMIT,
maybeAutoRenameBranchOnFirstWork,
resetFirstWorkBranchRenameState,
type FirstWorkBranchRenameDeps,
@@ -203,6 +204,41 @@ describe('maybeAutoRenameBranchOnFirstWork', () => {
expect(generateBranchNameMock).not.toHaveBeenCalled()
})
it('bounds settled worktree dedupe while keeping recent entries', async () => {
gitExecFileAsyncMock.mockImplementation(
gitResponder({ currentBranch: 'you/my-feature', hasUpstream: false })
)
const { deps } = makeDeps()
const firstWorktreeId = `${REPO_ID}${WORKTREE_ID_SEPARATOR}/repo/wt-0`
const lastWorktreeId = `${REPO_ID}${WORKTREE_ID_SEPARATOR}/repo/wt-${FIRST_WORK_BRANCH_RENAME_SETTLED_CACHE_LIMIT}`
for (let index = 0; index <= FIRST_WORK_BRANCH_RENAME_SETTLED_CACHE_LIMIT; index += 1) {
await maybeAutoRenameBranchOnFirstWork(
workingEvent({
tabId: undefined,
paneKey: '',
worktreeId: `${REPO_ID}${WORKTREE_ID_SEPARATOR}/repo/wt-${index}`
}),
deps
)
}
expect(gitExecFileAsyncMock).toHaveBeenCalledTimes(
FIRST_WORK_BRANCH_RENAME_SETTLED_CACHE_LIMIT + 1
)
gitExecFileAsyncMock.mockClear()
await maybeAutoRenameBranchOnFirstWork(
workingEvent({ tabId: undefined, paneKey: '', worktreeId: firstWorktreeId }),
deps
)
await maybeAutoRenameBranchOnFirstWork(
workingEvent({ tabId: undefined, paneKey: '', worktreeId: lastWorktreeId }),
deps
)
expect(gitExecFileAsyncMock).toHaveBeenCalledTimes(1)
})
it('retries on a later event after a transient failure (does not poison the worktree)', async () => {
generateBranchNameMock.mockResolvedValueOnce({ success: false, error: 'agent not ready' })
const { deps, onRenamed } = makeDeps()
@@ -68,6 +68,7 @@ export type FirstWorkBranchRenameDeps = {
// settled, so the real first prompt can still succeed on a later event.
const inFlightWorktreeIds = new Set<string>()
const settledWorktreeIds = new Set<string>()
export const FIRST_WORK_BRANCH_RENAME_SETTLED_CACHE_LIMIT = 500
/** Test seam: clear the per-process dedupe sets. */
export function resetFirstWorkBranchRenameState(): void {
@@ -75,6 +76,20 @@ export function resetFirstWorkBranchRenameState(): void {
settledWorktreeIds.clear()
}
function rememberSettledWorktreeId(worktreeId: string): void {
// Why: the app can see unbounded worktree ids over a long session; evicting
// oldest entries trades a rare re-probe for bounded process memory.
settledWorktreeIds.delete(worktreeId)
settledWorktreeIds.add(worktreeId)
while (settledWorktreeIds.size > FIRST_WORK_BRANCH_RENAME_SETTLED_CACHE_LIMIT) {
const oldest = settledWorktreeIds.values().next().value
if (typeof oldest !== 'string') {
return
}
settledWorktreeIds.delete(oldest)
}
}
export async function maybeAutoRenameBranchOnFirstWork(
event: FirstWorkBranchRenameEvent,
deps: FirstWorkBranchRenameDeps
@@ -107,7 +122,7 @@ export async function maybeAutoRenameBranchOnFirstWork(
// ineligible); false means a transient bail that should retry later.
const settled = await runAutoRename(worktreeId, prompt, event.assistantMessage, deps)
if (settled) {
settledWorktreeIds.add(worktreeId)
rememberSettledWorktreeId(worktreeId)
}
} catch (error) {
// Why: best-effort, opt-in convenience. A failure must never disrupt the