fix(routing): resolve unstamped local worktrees to the local host (STA-5683) (#16841)

* fix(routing): resolve unstamped local worktrees

* fix(routing): preserve remote worktree ownership

* fix(routing): restore empty-catalog local fallback
This commit is contained in:
Brennan Benson
2026-08-27 14:43:59 -07:00
committed by GitHub
parent 249d93bc5d
commit d3475957f3
8 changed files with 222 additions and 19 deletions
@@ -65,6 +65,25 @@ describe('resolveTerminalWorktreeRoute', () => {
})
})
it('routes and tears down an unstamped local worktree despite an unrelated saved runtime', () => {
const ownerlessWorktree = { id: 'repo-1::/w', repoId: 'repo-1' }
const state = localState({
repos: [{ id: 'repo-1' }],
worktreesByRepo: { 'repo-1': [ownerlessWorktree] },
detectedWorktreesByRepo: { 'repo-1': { worktrees: [ownerlessWorktree] } },
runtimeEnvironments: [{ id: 'saved-runtime' }],
settings: { activeRuntimeEnvironmentId: null }
} as unknown as Partial<AppState>)
expect(resolveTerminalWorktreeRoute(state, 'repo-1::/w')).toEqual({
runtimeEnvironmentId: null
})
expect(resolveTerminalHostOwnership(state, 'repo-1::/w', 'teardown')).toEqual({
kind: 'local-or-ssh',
runtimeEnvironmentId: null
})
})
it('still fails a genuinely unknown/stale worktree closed', () => {
expect(resolveTerminalWorktreeRoute(localState(), 'repo-9::/stale')).toBeNull()
})
@@ -155,19 +155,128 @@ describe('resolveWorktreeOperationRouteResult', () => {
expect(resolveWorktreeOperationRouteResult({}, WORKTREE_ID)).toEqual({ kind: 'missing' })
})
it('fails a paired-client ownerless stale publication closed instead of routing it locally', () => {
it('routes the reported unstamped local shape with one saved runtime', () => {
expect(
resolveWorktreeOperationRouteResult(
{
repos: [{ id: 'repo-1' } as never],
runtimeEnvironments: [{ id: 'disconnected-hub' }],
worktreesByRepo: { 'repo-1': [worktree(undefined)] }
runtimeEnvironments: [{ id: 'saved-runtime' }],
runtimeEnvironmentCatalogHydrated: true,
settings: { activeRuntimeEnvironmentId: null } as never,
worktreesByRepo: { 'repo-1': [worktree(undefined)] },
detectedWorktreesByRepo: {
'repo-1': { worktrees: [worktree(undefined)] }
}
},
WORKTREE_ID
)
).toEqual({
kind: 'resolved',
route: { executionHostId: 'local', runtimeEnvironmentId: null }
})
})
it('ignores the number of unrelated saved runtimes for unstamped local identity', () => {
expect(
resolveWorktreeOperationRouteResult(
{
repos: [{ id: 'repo-1' } as never],
runtimeEnvironments: [{ id: 'runtime-a' }, { id: 'runtime-b' }, { id: 'runtime-c' }],
runtimeEnvironmentCatalogHydrated: true,
settings: { activeRuntimeEnvironmentId: null } as never,
worktreesByRepo: { 'repo-1': [worktree(undefined)] },
detectedWorktreesByRepo: {
'repo-1': { worktrees: [worktree(undefined)] }
}
},
WORKTREE_ID
)
).toEqual({
kind: 'resolved',
route: { executionHostId: 'local', runtimeEnvironmentId: null }
})
})
it('does not treat a repo row alone as positive local identity', () => {
expect(
resolveWorktreeOperationRouteResult(
{
repos: [{ id: 'repo-1' } as never],
runtimeEnvironments: [{ id: 'saved-runtime' }],
runtimeEnvironmentCatalogHydrated: true
},
WORKTREE_ID
)
).toEqual({ kind: 'missing' })
})
it('fails a paired-client ownerless stale publication closed instead of routing it locally', () => {
expect(
resolveWorktreeOperationRouteResult(
{
worktreesByRepo: { 'repo-1': [worktree(undefined)] },
runtimeEnvironments: [{ id: 'saved-runtime' }],
runtimeEnvironmentCatalogHydrated: true
},
WORKTREE_ID
)
).toEqual({ kind: 'missing' })
})
it('keeps an unstamped row ambiguous when another owner names a different host', () => {
expect(
resolveWorktreeOperationRouteResult(
{
repos: [{ id: 'repo-1' } as never],
runtimeEnvironments: [{ id: 'saved-runtime' }],
runtimeEnvironmentCatalogHydrated: true,
worktreesByRepo: {
'repo-1': [worktree('local'), worktree('runtime:hub-a')]
}
},
WORKTREE_ID
)
).toEqual({ kind: 'ambiguous' })
})
it('routes a focused runtime only when it is the single saved runtime', () => {
expect(
resolveWorktreeOperationRouteResult(
{
settings: { activeRuntimeEnvironmentId: 'hub-a' } as never,
runtimeEnvironments: [{ id: 'hub-a' }],
runtimeEnvironmentCatalogHydrated: true,
worktreesByRepo: { 'repo-1': [worktree(undefined)] }
},
WORKTREE_ID
)
).toEqual({
kind: 'resolved',
route: { executionHostId: 'runtime:hub-a', runtimeEnvironmentId: 'hub-a' }
})
})
it('ignores unrelated removed-runtime tombstones for positive local identity', () => {
expect(
resolveWorktreeOperationRouteResult(
{
repos: [{ id: 'repo-1' } as never],
runtimeEnvironments: [{ id: 'saved-runtime' }],
runtimeEnvironmentCatalogHydrated: true,
removedRuntimeEnvironmentIds: new Set(['removed-runtime']),
worktreesByRepo: { 'repo-1': [worktree(undefined)] },
detectedWorktreesByRepo: {
'repo-1': { worktrees: [worktree(undefined)] }
}
},
WORKTREE_ID
)
).toEqual({
kind: 'resolved',
route: { executionHostId: 'local', runtimeEnvironmentId: null }
})
})
it('fails ownerless rows closed until the saved-runtime catalog is hydrated', () => {
expect(
resolveWorktreeOperationRouteResult(
@@ -198,6 +307,26 @@ describe('resolveWorktreeOperationRouteResult', () => {
})
it('preserves ownerless local compatibility after an empty catalog hydrates', () => {
expect(
resolveWorktreeOperationRouteResult(
{
repos: [{ id: 'repo-1' } as never],
runtimeEnvironments: [],
runtimeEnvironmentCatalogHydrated: true,
worktreesByRepo: { 'repo-1': [worktree(undefined)] },
detectedWorktreesByRepo: {
'repo-1': { worktrees: [worktree(undefined)] }
}
},
WORKTREE_ID
)
).toEqual({
kind: 'resolved',
route: { executionHostId: 'local', runtimeEnvironmentId: null }
})
})
it('preserves ownerless local compatibility before detected scan with no saved runtimes', () => {
expect(
resolveWorktreeOperationRouteResult(
{
@@ -192,9 +192,10 @@ export function resolveWorktreeOperationRouteResult(
return explicitResolution
}
const hasDetectedWorktree = hasIndexedDetectedWorktree(state.detectedWorktreesByRepo, worktreeId)
const hasKnownWorktree =
resolveIndexedWorktreeOwner(state.worktreesByRepo, worktreeId).kind !== 'missing' ||
hasIndexedDetectedWorktree(state.detectedWorktreesByRepo, worktreeId)
hasDetectedWorktree
const repoId = getRepoIdFromWorktreeId(worktreeId)
const hasKnownRepo = state.repos?.some((repo) => repo.id === repoId) === true
if (!hasKnownWorktree && !hasKnownRepo) {
@@ -219,10 +220,11 @@ export function resolveWorktreeOperationRouteResult(
}
}
}
// Why: no saved runtime can publish a remote ownerless row; otherwise current detected presence affirms identity under the stamped-writer invariant.
const mayBeLegacyLocal =
(savedRuntimeIds === undefined ||
(state.runtimeEnvironmentCatalogHydrated === true && savedRuntimeIds.length === 0)) &&
(state.removedRuntimeEnvironmentIds?.size ?? 0) === 0
savedRuntimeIds === undefined ||
(state.runtimeEnvironmentCatalogHydrated === true &&
(savedRuntimeIds.length === 0 || hasDetectedWorktree))
return mayBeLegacyLocal
? { kind: 'resolved', route: { executionHostId: 'local', runtimeEnvironmentId: null } }
: { kind: 'missing' }
@@ -45,7 +45,7 @@ describe('fetchWorktrees', () => {
clearHugeRepoWarningDismissalsForTests()
})
it('fetches worktrees from the active remote runtime environment', async () => {
it('stamps ownerless worktrees from an older active remote runtime before repos hydrate', async () => {
const store = createTestStore()
const remote = makeWorktree({
id: 'repo1::/remote/wt1',
@@ -63,7 +63,15 @@ describe('fetchWorktrees', () => {
await store.getState().fetchWorktrees('repo1')
expect(store.getState().worktreesByRepo.repo1).toEqual([remote])
const expected = {
...remote,
hostId: 'runtime:env-1',
runtimeOwnerEnvironmentId: 'env-1'
}
expect(store.getState().worktreesByRepo.repo1).toEqual([expected])
expect(store.getState().detectedWorktreesByRepo.repo1?.worktrees).toEqual([
expect.objectContaining(expected)
])
expect(runtimeEnvironmentCall).toHaveBeenCalledWith({
selector: 'env-1',
method: 'worktree.detectedList',
@@ -581,12 +589,26 @@ describe('fetchWorktrees', () => {
await store.getState().fetchWorktrees('repo1')
expect(store.getState().worktreesByRepo.repo1).toEqual([remote])
expect(store.getState().worktreesByRepo.repo1).toEqual([
{
...remote,
hostId: 'runtime:env-1',
runtimeOwnerEnvironmentId: 'env-1'
}
])
expect(store.getState().detectedWorktreesByRepo.repo1).toMatchObject({
repoId: 'repo1',
authoritative: true,
source: 'session-fallback',
worktrees: [{ id: remote.id, ownership: 'orca-managed', visible: true }]
worktrees: [
{
id: remote.id,
ownership: 'orca-managed',
visible: true,
hostId: 'runtime:env-1',
runtimeOwnerEnvironmentId: 'env-1'
}
]
})
expect(runtimeEnvironmentCall).toHaveBeenCalledWith({
selector: 'env-1',
@@ -44,7 +44,9 @@ describe('fetchWorktrees', () => {
id: 'repo1::/remote/wt1',
repoId: 'repo1',
path: '/remote/wt1',
branch: 'refs/heads/remote'
branch: 'refs/heads/remote',
hostId: 'runtime:env-1',
runtimeOwnerEnvironmentId: 'env-1'
})
const lineage = makeLineage({ worktreeId: initial.id })
const refreshed = { ...initial, lineage }
@@ -100,7 +102,9 @@ describe('fetchWorktrees', () => {
id: 'repo1::/remote/wt1',
repoId: 'repo1',
path: '/remote/wt1',
branch: 'refs/heads/remote'
branch: 'refs/heads/remote',
hostId: 'runtime:env-1',
runtimeOwnerEnvironmentId: 'env-1'
})
const staleLineage = makeLineage({
worktreeId: worktree.id,
@@ -292,7 +296,13 @@ describe('fetchWorktrees', () => {
await store.getState().fetchWorktrees('repo1')
expect(store.getState().worktreesByRepo.repo1).toEqual([refreshed])
expect(store.getState().worktreesByRepo.repo1).toEqual([
{
...refreshed,
hostId: 'runtime:env-1',
runtimeOwnerEnvironmentId: 'env-1'
}
])
expect(store.getState().worktreeLineageById).toEqual({
[staleLineage.worktreeId]: staleLineage
})
@@ -453,7 +453,11 @@ describe('worktree lineage state', () => {
})
expect(mockApi.worktrees.updateLineage).not.toHaveBeenCalled()
expect(store.getState().worktreeLineageById).toEqual({ [lineage.worktreeId]: lineage })
expect(store.getState().worktreesByRepo.repo1?.[0]).toEqual(updatedChild)
expect(store.getState().worktreesByRepo.repo1?.[0]).toEqual({
...updatedChild,
hostId: 'runtime:env-1',
runtimeOwnerEnvironmentId: 'env-1'
})
expect(store.getState().sortEpoch).toBe(4)
})
@@ -537,7 +541,11 @@ describe('worktree lineage state', () => {
})
expect(mockApi.worktrees.updateLineage).not.toHaveBeenCalled()
expect(store.getState().worktreeLineageById).toEqual({ [lineage.worktreeId]: lineage })
expect(store.getState().worktreesByRepo.repo1?.[0]).toEqual(updatedChild)
expect(store.getState().worktreesByRepo.repo1?.[0]).toEqual({
...updatedChild,
hostId: 'runtime:env-1',
runtimeOwnerEnvironmentId: 'env-1'
})
expect(store.getState().sortEpoch).toBe(4)
runtimeEnvironmentCall
@@ -701,7 +709,11 @@ describe('worktree lineage state', () => {
timeoutMs: 15_000
})
expect(store.getState().worktreeLineageById).toEqual({})
expect(store.getState().worktreesByRepo.repo1?.[0]).toEqual(updatedChild)
expect(store.getState().worktreesByRepo.repo1?.[0]).toEqual({
...updatedChild,
hostId: 'runtime:env-1',
runtimeOwnerEnvironmentId: 'env-1'
})
})
// An unresolvable owner route must reach the caller so the sidebar can toast it, rather than
@@ -90,7 +90,13 @@ describe('worktree remote runtime mutations', () => {
timeoutMs: 10 * 60_000
})
expect(mockApi.worktrees.create).not.toHaveBeenCalled()
expect(store.getState().worktreesByRepo.repo1).toEqual([wt])
expect(store.getState().worktreesByRepo.repo1).toEqual([
{
...wt,
hostId: 'runtime:env-1',
runtimeOwnerEnvironmentId: 'env-1'
}
])
})
it('forwards generated-name provenance through paired-runtime create', async () => {
@@ -6,6 +6,7 @@ import { reuseEqualCatalogRows } from '../../worktree-catalog-reconciliation'
import { getRepoIdFromWorktreeId } from '../../worktree-helpers'
import {
getRepoExecutionHostId,
getSettingsFocusedExecutionHostId,
LOCAL_EXECUTION_HOST_ID,
parseExecutionHostId,
toSshExecutionHostId,
@@ -60,7 +61,9 @@ export function repoHostId(
if (repo) {
return getRepoExecutionHostId(repo)
}
return hostId && parseExecutionHostId(hostId) ? hostId : LOCAL_EXECUTION_HOST_ID
return hostId && parseExecutionHostId(hostId)
? hostId
: getSettingsFocusedExecutionHostId(state.settings)
}
export function repoHasExactlyOneExecutionHostOwner(