From 7fad71e44842e7de293db3300f4a0eefb6040412 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Mon, 17 Aug 2026 01:23:08 -0700 Subject: [PATCH] fix(worktree): skip retirement backfill on every non-local host (#15023) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The backfill guard tested repo.connectionId, but a runtime-owned repo carries executionHostId with no connectionId, so it read as local. The scan then walked this machine's workspace and agent-transcript directories and filed the result under the runtime host's namespace — retiring names never used there while missing the ones that were. Guard on the execution host id instead. Ongoing retirement was already correct for these repos; only the one-time historical seed was wrong. --- src/main/worktree-name-retirement.test.ts | 56 +++++++++++++++++++++++ src/main/worktree-name-retirement.ts | 11 +++-- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/main/worktree-name-retirement.test.ts b/src/main/worktree-name-retirement.test.ts index 8a0e8237caf..e7c1c249c56 100644 --- a/src/main/worktree-name-retirement.test.ts +++ b/src/main/worktree-name-retirement.test.ts @@ -336,4 +336,60 @@ describe('ensureRetiredWorktreeNamesBackfilled', () => { expect(merged).toEqual([]) }) + + it('skips a runtime-owned repo, which has no connectionId but is still not local', async () => { + // Why: a `connectionId` check calls this repo local, so the scan reads THIS machine's + // directories and files them under the runtime's namespace — retiring names never used there + // while missing the ones that were. The host id is the only reliable local test. + const root = await mkdtemp(join(tmpdir(), 'orca-retirement-runtime-')) + const workspaceRoot = join(root, 'workspaces') + await mkdir(join(workspaceRoot, FIRST), { recursive: true }) + const merged: string[] = [] + const store = { + mergeRetiredWorktreeNames: (_repoId: string, names: Iterable) => { + merged.push(...names) + return true + } + } + const runtimeRepo = { + ...makeRepo('repo-runtime', '/repos/runtime'), + executionHostId: 'runtime:env-1' + } as Repo + + try { + const collisionKey = await ensureRetiredWorktreeNamesBackfilled(store, runtimeRepo, { + workspaceDir: workspaceRoot, + nestWorkspaces: false + }) + + expect(merged).toEqual([]) + expect(collisionKey).toBeNull() + } finally { + await rm(root, { force: true, recursive: true }) + } + }) + + it('still backfills a plain local repo, so the skip is scoped to non-local hosts', async () => { + const root = await mkdtemp(join(tmpdir(), 'orca-retirement-local-')) + const workspaceRoot = join(root, 'workspaces') + await mkdir(join(workspaceRoot, FIRST), { recursive: true }) + const merged: string[] = [] + const store = { + mergeRetiredWorktreeNames: (_repoId: string, names: Iterable) => { + merged.push(...names) + return true + } + } + + try { + await ensureRetiredWorktreeNamesBackfilled(store, makeRepo('repo-local', '/repos/local'), { + workspaceDir: workspaceRoot, + nestWorkspaces: false + }) + + expect(merged).toEqual([FIRST]) + } finally { + await rm(root, { force: true, recursive: true }) + } + }) }) diff --git a/src/main/worktree-name-retirement.ts b/src/main/worktree-name-retirement.ts index 53ac593011d..dda9a2e0110 100644 --- a/src/main/worktree-name-retirement.ts +++ b/src/main/worktree-name-retirement.ts @@ -1,7 +1,7 @@ import { readdir } from 'node:fs/promises' import { homedir } from 'node:os' import { join } from 'node:path' -import { getRepoExecutionHostId } from '../shared/execution-host' +import { getRepoExecutionHostId, LOCAL_EXECUTION_HOST_ID } from '../shared/execution-host' import { creatureNameTier, EMPTY_RETIRED_NAME_REGISTRY, @@ -239,8 +239,13 @@ export async function ensureRetiredWorktreeNamesBackfilled( settings: RetirementPathSettings ): Promise { // Remote workspaces keep their agent state on the execution host, which this scan cannot see, so - // a re-added SSH repo does not recover its retirements the way a local one does. - if (isFolderRepo(repo) || repo.connectionId) { + // a re-added remote repo does not recover its retirements the way a local one does. + // + // Why the host id and not `connectionId`: a runtime-owned repo carries `executionHostId` with no + // `connectionId`, so a connectionId check calls it local, scans THIS machine's directories, and + // files the result under the runtime's namespace — retiring names never used there while missing + // the ones that were. + if (isFolderRepo(repo) || getRepoExecutionHostId(repo) !== LOCAL_EXECUTION_HOST_ID) { return null } const probePath = await computeWorktreePathAsync(