perf(codex): skip migration scans for nonshared reattaches

This commit is contained in:
Neil
2026-08-11 00:23:49 -07:00
parent bb08b66b21
commit 0a8e2b17df
9 changed files with 376 additions and 50 deletions
@@ -225,6 +225,13 @@ export function getCodexPaneAccount(ptyId: string): CodexPaneAccountRecord | nul
return readRegistry().panes[ptyId] ?? null
}
/** `custom-home` stays conservative because it can mask a non-comparable shared-home route. */
export function isCodexPaneHomeRouteProvenAwayFromSharedHome(
route: CodexPaneHomeRoute | undefined
): boolean {
return route === 'real-home' || route === 'account-home' || route === 'wsl-home'
}
/**
* Reports the lane each given PTY launched from, omitting panes with no record.
*
@@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest'
import { CodexSessionMigrationRecentExits } from './codex-session-migration-recent-exits'
describe('CodexSessionMigrationRecentExits', () => {
it('refreshes a rerecorded ID before evicting the oldest entry', () => {
const exits = new CodexSessionMigrationRecentExits()
for (let index = 0; index < 256; index += 1) {
exits.record(`pty-${index}`, index + 1)
}
exits.record('pty-0', 500)
exits.record('pty-overflow', 501)
expect(exits.matchesAfter('pty-0', 499)).toBe(true)
expect(exits.matchesAfter('pty-1', 0)).toBe(false)
})
})
@@ -0,0 +1,52 @@
const RETENTION_MS = 60_000
const MAX_ENTRIES = 256
type RecentPtyExit = { sequence: number; recordedAt: number }
export class CodexSessionMigrationRecentExits {
private readonly exits = new Map<string, RecentPtyExit>()
record(leaseId: string, sequence: number): void {
const now = Date.now()
for (const [id, exit] of this.exits) {
if (now - exit.recordedAt > RETENTION_MS) {
this.exits.delete(id)
}
}
this.exits.delete(leaseId)
this.exits.set(leaseId, { sequence, recordedAt: now })
while (this.exits.size > MAX_ENTRIES) {
const oldestLeaseId = this.exits.keys().next().value
if (oldestLeaseId === undefined) {
break
}
this.exits.delete(oldestLeaseId)
}
}
consumeAfter(leaseId: string, startedSequence: number | undefined): RecentPtyExit | null {
const exit = this.exits.get(leaseId)
this.exits.delete(leaseId)
if (
!exit ||
startedSequence === undefined ||
exit.sequence <= startedSequence ||
Date.now() - exit.recordedAt > RETENTION_MS
) {
return null
}
return exit
}
matchesAfter(leaseId: string, startedSequence: number | undefined): boolean {
const exit = this.exits.get(leaseId)
if (!exit) {
return false
}
if (Date.now() - exit.recordedAt > RETENTION_MS) {
this.exits.delete(leaseId)
return false
}
return startedSequence !== undefined && exit.sequence > startedSequence
}
}
@@ -491,6 +491,146 @@ describe('createCodexSessionMigrationScheduler', () => {
await vi.waitFor(() => expect(finishScheduledRun).toHaveBeenCalledOnce())
})
it('keeps an ignored reattach exit from poisoning a same-ID launch already starting', async () => {
const startBackfill = vi.fn().mockResolvedValue({ stopped: false })
const scheduler = createCodexSessionMigrationScheduler({
isEligible: () => true,
isQuitting: () => false,
resolveSystemCodexHomePathOverride: () => undefined,
startBackfill,
startIndexHeal: vi.fn().mockResolvedValue(null),
initialDelayMs: 1_000
})
scheduler.ignoreLaunch('stable-pty', 1)
scheduler.finishLaunch('stable-pty', 3)
scheduler.beginLaunch('stable-pty', false, new Date(), 2)
await vi.advanceTimersByTimeAsync(1_000)
expect(startBackfill).toHaveBeenCalledWith(
expect.objectContaining({
scanDates: expect.any(Array),
writeCompletionMarker: false,
writeBoundedCompletionMarker: false
}),
undefined
)
})
it('matches an ignored reattach exit that arrived before its lifecycle callback', async () => {
const startBackfill = vi.fn().mockResolvedValue({ stopped: false })
const scheduler = createCodexSessionMigrationScheduler({
isEligible: () => true,
isQuitting: () => false,
resolveSystemCodexHomePathOverride: () => undefined,
startBackfill,
startIndexHeal: vi.fn().mockResolvedValue(null),
initialDelayMs: 1_000
})
scheduler.finishLaunch('stable-pty', 2)
scheduler.ignoreLaunch('stable-pty', 1)
scheduler.beginLaunch('stable-pty', false, new Date(), 3)
await vi.advanceTimersByTimeAsync(1_000)
expect(startBackfill).toHaveBeenCalledWith(
expect.objectContaining({
scanDates: expect.any(Array),
writeCompletionMarker: false,
writeBoundedCompletionMarker: false
}),
undefined
)
})
it('keeps a newer same-ID launch active while the ignored incarnation exits', async () => {
const startBackfill = vi.fn().mockResolvedValue({ stopped: false })
const scheduler = createCodexSessionMigrationScheduler({
isEligible: () => true,
isQuitting: () => false,
resolveSystemCodexHomePathOverride: () => undefined,
startBackfill,
startIndexHeal: vi.fn().mockResolvedValue(null),
initialDelayMs: 1_000
})
scheduler.ignoreLaunch('stable-pty', 1)
scheduler.beginLaunch('stable-pty', false, new Date(), 2)
scheduler.finishLaunch('stable-pty', 3)
await vi.advanceTimersByTimeAsync(1_000)
expect(startBackfill).toHaveBeenCalledWith(
expect.objectContaining({ writeCompletionMarker: false }),
undefined
)
scheduler.finishLaunch('stable-pty', 4)
await vi.advanceTimersByTimeAsync(1_000)
expect(startBackfill).toHaveBeenLastCalledWith(
expect.objectContaining({ writeCompletionMarker: true }),
undefined
)
})
it('matches every late duplicate ignored callback without poisoning reuse', async () => {
const startBackfill = vi.fn().mockResolvedValue({ stopped: false })
const scheduler = createCodexSessionMigrationScheduler({
isEligible: () => true,
isQuitting: () => false,
resolveSystemCodexHomePathOverride: () => undefined,
startBackfill,
startIndexHeal: vi.fn().mockResolvedValue(null),
initialDelayMs: 1_000
})
scheduler.ignoreLaunch('stable-pty', 1)
scheduler.finishLaunch('stable-pty', 3)
expect(scheduler.matchesIgnoredLaunchExit('stable-pty', 2)).toBe(true)
expect(scheduler.matchesIgnoredLaunchExit('stable-pty', 2)).toBe(true)
scheduler.beginLaunch('stable-pty', false, new Date(), 4)
await vi.advanceTimersByTimeAsync(1_000)
expect(startBackfill).toHaveBeenCalledTimes(1)
scheduler.finishLaunch('stable-pty', 5)
await vi.advanceTimersByTimeAsync(1_000)
expect(startBackfill).toHaveBeenCalledTimes(2)
expect(startBackfill).toHaveBeenLastCalledWith(
expect.objectContaining({ writeCompletionMarker: true }),
undefined
)
})
it('keeps three exit-before-callback reattaches from stranding a reused ID', async () => {
const startBackfill = vi.fn().mockResolvedValue({ stopped: false })
const scheduler = createCodexSessionMigrationScheduler({
isEligible: () => true,
isQuitting: () => false,
resolveSystemCodexHomePathOverride: () => undefined,
startBackfill,
startIndexHeal: vi.fn().mockResolvedValue(null),
initialDelayMs: 1_000
})
scheduler.finishLaunch('stable-pty', 4)
scheduler.ignoreLaunch('stable-pty', 1)
scheduler.ignoreLaunch('stable-pty', 2)
scheduler.ignoreLaunch('stable-pty', 3)
scheduler.beginLaunch('stable-pty', false, new Date(), 5)
await vi.advanceTimersByTimeAsync(1_000)
expect(startBackfill).toHaveBeenLastCalledWith(
expect.objectContaining({ writeCompletionMarker: false }),
undefined
)
scheduler.finishLaunch('stable-pty', 6)
await vi.advanceTimersByTimeAsync(1_000)
expect(startBackfill).toHaveBeenCalledTimes(2)
expect(startBackfill).toHaveBeenLastCalledWith(
expect.objectContaining({ writeCompletionMarker: true }),
undefined
)
})
it('does not consume an exit from an earlier stable-id incarnation', async () => {
const startBackfill = vi.fn().mockResolvedValue({ stopped: false })
const scheduler = createCodexSessionMigrationScheduler({
@@ -1,4 +1,5 @@
import { getCodexSessionBackfillDate } from './codex-session-backfill-date'
import { CodexSessionMigrationRecentExits } from './codex-session-migration-recent-exits'
import type {
CodexSessionBackfillDate,
CodexSessionBackfillOptions
@@ -9,11 +10,6 @@ type MigrationRun = (
systemCodexHomePathOverride?: string
) => Promise<unknown>
const EARLY_PTY_EXIT_RETENTION_MS = 60_000
const MAX_EARLY_PTY_EXITS = 256
type EarlyPtyExit = { sequence: number; recordedAt: number }
export type CodexSessionMigrationScheduler = {
beginLaunch(
leaseId: string,
@@ -21,6 +17,8 @@ export type CodexSessionMigrationScheduler = {
startedAt?: Date,
startedSequence?: number
): void
ignoreLaunch(leaseId: string, startedSequence: number): void
matchesIgnoredLaunchExit(leaseId: string, startedSequence: number): boolean
finishLaunch(leaseId: string, exitSequence?: number): void
scheduleInitialRun(): void
scheduleRun(fullScanRequired?: boolean): void
@@ -46,7 +44,10 @@ export function createCodexSessionMigrationScheduler(args: {
let pendingFullScan = false
let migrationTask: Promise<void> | null = null
const activeLaunches = new Map<string, Date>()
const earlyPtyExits = new Map<string, EarlyPtyExit>()
// Why: non-shared reattach exits must not masquerade as exit-before-begin races for a reused id.
const ignoredLaunches = new Set<string>()
const ignoredPtyExits = new CodexSessionMigrationRecentExits()
const earlyPtyExits = new CodexSessionMigrationRecentExits()
let activeRunStopObserved = false
let rerunRequested = false
@@ -194,18 +195,41 @@ export function createCodexSessionMigrationScheduler(args: {
if (args.isQuitting() || activeLaunches.has(leaseId)) {
return
}
if (consumeEarlyPtyExit(earlyPtyExits, leaseId, startedSequence)) {
if (earlyPtyExits.consumeAfter(leaseId, startedSequence)) {
scheduleRun(true)
return
}
activeLaunches.set(leaseId, startedAt)
scheduleRun(fullScanRequired)
},
ignoreLaunch(leaseId, startedSequence): void {
if (args.isQuitting() || ignoredLaunches.has(leaseId)) {
return
}
if (ignoredPtyExits.matchesAfter(leaseId, startedSequence)) {
return
}
const earlyExit = earlyPtyExits.consumeAfter(leaseId, startedSequence)
if (earlyExit) {
ignoredPtyExits.record(leaseId, earlyExit.sequence)
return
}
ignoredLaunches.add(leaseId)
},
matchesIgnoredLaunchExit(leaseId, startedSequence): boolean {
return ignoredPtyExits.matchesAfter(leaseId, startedSequence)
},
finishLaunch(leaseId, exitSequence): void {
if (ignoredLaunches.delete(leaseId)) {
if (exitSequence !== undefined) {
ignoredPtyExits.record(leaseId, exitSequence)
}
return
}
const startedAt = activeLaunches.get(leaseId)
if (!startedAt) {
if (exitSequence !== undefined) {
recordEarlyPtyExit(earlyPtyExits, leaseId, exitSequence)
earlyPtyExits.record(leaseId, exitSequence)
}
return
}
@@ -253,42 +277,6 @@ function compareBackfillDates(
return left.join('-').localeCompare(right.join('-'))
}
function recordEarlyPtyExit(
exits: Map<string, EarlyPtyExit>,
leaseId: string,
sequence: number
): void {
const now = Date.now()
for (const [id, exit] of exits) {
if (now - exit.recordedAt > EARLY_PTY_EXIT_RETENTION_MS) {
exits.delete(id)
}
}
exits.set(leaseId, { sequence, recordedAt: now })
while (exits.size > MAX_EARLY_PTY_EXITS) {
const oldestLeaseId = exits.keys().next().value
if (oldestLeaseId === undefined) {
break
}
exits.delete(oldestLeaseId)
}
}
function consumeEarlyPtyExit(
exits: Map<string, EarlyPtyExit>,
leaseId: string,
startedSequence: number | undefined
): boolean {
const exit = exits.get(leaseId)
exits.delete(leaseId)
return (
exit !== undefined &&
startedSequence !== undefined &&
exit.sequence > startedSequence &&
Date.now() - exit.recordedAt <= EARLY_PTY_EXIT_RETENTION_MS
)
}
function isStoppedMigrationResult(result: unknown): boolean {
return Boolean(result && typeof result === 'object' && 'stopped' in result && result.stopped)
}
@@ -9,6 +9,7 @@ import {
getCodexPaneAccount,
hasRecordedLegacySharedCodexPane,
hasRecordedManagedHostCodexPane,
isCodexPaneHomeRouteProvenAwayFromSharedHome,
reconcileCodexPaneAccountsWithLivePtys,
recordCodexPaneAccount
} from './codex-pane-account-registry'
@@ -47,6 +48,17 @@ afterEach(() => {
})
describe('codex pane account registry', () => {
it.each([
['real-home', true],
['account-home', true],
['wsl-home', true],
['shared-home', false],
['custom-home', false],
[undefined, false]
] as const)('classifies whether %s proves a pane avoided the shared home', (route, expected) => {
expect(isCodexPaneHomeRouteProvenAwayFromSharedHome(route)).toBe(expected)
})
it('survives a process restart so a daemon-backed shell stays attributable', () => {
recordCodexPaneAccount('pty-1', {
selectionKey: 'host',
+23
View File
@@ -39,7 +39,10 @@ import {
shutdownDaemon
} from './daemon/daemon-init'
import {
type CodexPaneHomeRoute,
getCodexPaneAccount,
hasRecordedManagedHostCodexPane,
isCodexPaneHomeRouteProvenAwayFromSharedHome,
reconcileCodexPaneAccountsWithLivePtys
} from './codex/codex-pane-account-registry'
import { closeAllWatchers } from './ipc/filesystem-watcher'
@@ -401,10 +404,30 @@ function handleCodexHomePtySpawned(args: {
id: string
codexHomePath: string | null
reattached?: boolean
reattachedHomeRoute?: CodexPaneHomeRoute | null
launchEnv?: NodeJS.ProcessEnv
startedAt?: Date
startedSequence?: number
}): void {
// Why: only shared or ambiguous retained shells can create rollout logs that still need publication.
if (args.reattached && args.startedSequence !== undefined) {
const paneAccount = getCodexPaneAccount(args.id)
const homeRoute =
args.reattachedHomeRoute !== undefined
? (args.reattachedHomeRoute ?? undefined)
: paneAccount?.homeRoute
if (codexSessionMigration && isCodexPaneHomeRouteProvenAwayFromSharedHome(homeRoute)) {
codexSessionMigration.ignoreLaunch(args.id, args.startedSequence)
return
}
if (
!paneAccount &&
homeRoute === undefined &&
codexSessionMigration?.matchesIgnoredLaunchExit(args.id, args.startedSequence)
) {
return
}
}
const fullScanRequired =
codexRuntimeHome?.beginHostSystemDefaultSessionMigrationLaunch(args.codexHomePath, {
reattached: args.reattached,
+15 -4
View File
@@ -70,6 +70,7 @@ const {
clearPaneKeyAliasesForPtyMock,
recordCodexPaneAccountMock,
forgetCodexPaneAccountMock,
getCodexPaneAccountMock,
ensureCodexBackfillRecoveryMock
} = vi.hoisted(() => ({
handleMock: vi.fn(),
@@ -107,6 +108,7 @@ const {
clearPaneKeyAliasesForPtyMock: vi.fn(),
recordCodexPaneAccountMock: vi.fn(),
forgetCodexPaneAccountMock: vi.fn(),
getCodexPaneAccountMock: vi.fn(),
ensureCodexBackfillRecoveryMock: vi.fn(() => Promise.resolve())
}))
@@ -221,7 +223,8 @@ vi.mock('../agent-hooks/migration-unsupported-pty-state', () => ({
vi.mock('../codex/codex-pane-account-registry', () => ({
recordCodexPaneAccount: recordCodexPaneAccountMock,
forgetCodexPaneAccount: forgetCodexPaneAccountMock
forgetCodexPaneAccount: forgetCodexPaneAccountMock,
getCodexPaneAccount: getCodexPaneAccountMock
}))
vi.mock('../codex/codex-state-db-backfill-recovery', () => ({
@@ -410,6 +413,7 @@ describe('registerPtyHandlers', () => {
clearPaneKeyAliasesForPtyMock.mockReset()
recordCodexPaneAccountMock.mockReset()
forgetCodexPaneAccountMock.mockReset()
getCodexPaneAccountMock.mockReset()
ensureCodexBackfillRecoveryMock.mockReset()
ensureCodexBackfillRecoveryMock.mockResolvedValue(undefined)
mainWindow.webContents.on.mockReset()
@@ -9092,16 +9096,20 @@ describe('registerPtyHandlers', () => {
{
label: 'git worktree',
worktreeId: 'repo-1::/tmp/live-owner',
cwd: '/tmp/live-owner'
cwd: '/tmp/live-owner',
paneAccountAtStart: { homeRoute: 'shared-home' },
expectedHomeRouteAtStart: 'shared-home'
},
{
label: 'folder workspace',
worktreeId: 'folder:live-owner',
cwd: '/tmp'
cwd: '/tmp',
paneAccountAtStart: null,
expectedHomeRouteAtStart: null
}
])(
'adopts a completed runtime-owned pane before replacement launch preflight ($label)',
async ({ worktreeId, cwd }) => {
async ({ worktreeId, cwd, paneAccountAtStart, expectedHomeRouteAtStart }) => {
type StableAdoption = {
result: { id: string; incarnationId?: string; isReattach?: boolean }
owner: { handle?: string; tabId: string; leafId: string; ptyId: string }
@@ -9308,8 +9316,10 @@ describe('registerPtyHandlers', () => {
runtime.beginPtyRegistration.mockImplementation(() => {
runtimeSecondAdoption ??= spawnController.adoptStablePane(adoptionArgs)
})
getCodexPaneAccountMock.mockReturnValue(paneAccountAtStart)
const rendererFirstMount = handlers.get('pty:spawn')!(null, mountArgs)
await vi.waitFor(() => expect(providerSpawn).toHaveBeenCalledTimes(3))
getCodexPaneAccountMock.mockReturnValue({ homeRoute: 'real-home' })
releaseAttach()
await vi.waitFor(() => expect(runtimeSecondAdoption).not.toBeNull())
const pendingRuntimeAdoption = runtimeSecondAdoption
@@ -9354,6 +9364,7 @@ describe('registerPtyHandlers', () => {
id: 'pty-live-owner',
codexHomePath: null,
reattached: true,
reattachedHomeRoute: expectedHomeRouteAtStart,
startedAt: expect.any(Date),
startedSequence: expect.any(Number)
})
+78 -2
View File
@@ -198,7 +198,9 @@ import {
} from '../codex-accounts/managed-codex-auth-readiness'
import {
forgetCodexPaneAccount,
recordCodexPaneAccount
getCodexPaneAccount,
recordCodexPaneAccount,
type CodexPaneHomeRoute
} from '../codex/codex-pane-account-registry'
import { resolveCodexPaneLaunchAccount } from '../codex/codex-pane-launch-account'
import { getSystemCodexHomePath } from '../codex/codex-home-paths'
@@ -1238,6 +1240,7 @@ export type CodexHomePtySpawnedLifecycleArgs = {
id: string
codexHomePath: string | null
reattached?: boolean
reattachedHomeRoute?: CodexPaneHomeRoute | null
launchEnv?: NodeJS.ProcessEnv
startedAt?: Date
startedSequence?: number
@@ -1449,6 +1452,30 @@ function recordCodexPaneAccountForSpawn(args: {
recordCodexPaneAccount(args.ptyId, record)
}
function snapshotCodexPaneHomeRoutes(
ptyIds: readonly (string | null | undefined)[]
): ReadonlyMap<string, CodexPaneHomeRoute | null> {
const routes = new Map<string, CodexPaneHomeRoute | null>()
for (const ptyId of ptyIds) {
if (!ptyId || routes.has(ptyId)) {
continue
}
routes.set(ptyId, getCodexPaneAccount(ptyId)?.homeRoute ?? null)
}
return routes
}
function codexReattachedHomeRouteField(
routes: ReadonlyMap<string, CodexPaneHomeRoute | null>,
ptyId: string,
reattached: boolean
): { reattachedHomeRoute?: CodexPaneHomeRoute | null } {
if (!reattached || !routes.has(ptyId)) {
return {}
}
return { reattachedHomeRoute: routes.get(ptyId) ?? null }
}
function readEnvWithProcessFallback(
baseEnv: Record<string, string>,
key: string
@@ -4407,6 +4434,12 @@ export function registerPtyHandlers(
const codexHomeLaunchStartedAt = !args.connectionId ? new Date() : undefined
const codexHomeLaunchStartedSequence = !args.connectionId ? ++ptyLifecycleSequence : undefined
const preAdoptedStablePane = args.adoptedStablePane ?? null
const reattachedCodexHomeRoutes = !args.connectionId
? snapshotCodexPaneHomeRoutes([
preAdoptedStablePane?.result.id,
args.sessionId ? getAppPtyId(args.connectionId, args.sessionId) : undefined
])
: new Map<string, CodexPaneHomeRoute | null>()
const startupPromise = getLocalPtyStartupPromise(args.connectionId)
if (startupPromise) {
await startupPromise
@@ -4440,7 +4473,8 @@ export function registerPtyHandlers(
codexHomePath: null,
reattached: true,
startedAt: codexHomeLaunchStartedAt,
startedSequence: codexHomeLaunchStartedSequence
startedSequence: codexHomeLaunchStartedSequence,
...codexReattachedHomeRouteField(reattachedCodexHomeRoutes, result.id, true)
})
}
return result
@@ -5115,6 +5149,7 @@ export function registerPtyHandlers(
reattached: true,
startedAt: codexHomeLaunchStartedAt,
startedSequence: codexHomeLaunchStartedSequence,
...codexReattachedHomeRouteField(reattachedCodexHomeRoutes, result.id, true),
...(env ? { launchEnv: env } : {})
})
}
@@ -5288,6 +5323,11 @@ export function registerPtyHandlers(
codexHomePath: selectedCodexHomePath,
startedAt: codexHomeLaunchStartedAt,
startedSequence: codexHomeLaunchStartedSequence,
...codexReattachedHomeRouteField(
reattachedCodexHomeRoutes,
result.id,
result.isReattach === true
),
...(result.isReattach === true ? { reattached: true } : env ? { launchEnv: env } : {})
})
}
@@ -5786,6 +5826,37 @@ export function registerPtyHandlers(
) => {
const codexHomeLaunchStartedAt = !args.connectionId ? new Date() : undefined
const codexHomeLaunchStartedSequence = !args.connectionId ? ++ptyLifecycleSequence : undefined
const initialLeafId =
typeof args.leafId === 'string' && isTerminalLeafId(args.leafId) ? args.leafId : null
const initialPaneKey =
typeof args.worktreeId === 'string' &&
typeof args.tabId === 'string' &&
isValidTerminalTabId(args.tabId) &&
args.tabId.length <= 512 &&
initialLeafId
? makePaneKey(args.tabId, initialLeafId)
: null
const initialStablePanePtyId = (() => {
try {
return !args.connectionId && initialPaneKey
? resolveStablePaneOwner(
runtime,
store,
initialPaneKey,
args.worktreeId,
args.connectionId
)?.ptyId
: undefined
} catch {
return undefined
}
})()
const reattachedCodexHomeRoutes = !args.connectionId
? snapshotCodexPaneHomeRoutes([
initialStablePanePtyId,
args.sessionId ? getAppPtyId(args.connectionId, args.sessionId) : undefined
])
: new Map<string, CodexPaneHomeRoute | null>()
const spawnTiming = createPtySpawnTiming()
const startupPromise = getLocalPtyStartupPromise(args.connectionId)
if (startupPromise) {
@@ -6840,6 +6911,11 @@ export function registerPtyHandlers(
codexHomePath: selectedCodexHomePath,
startedAt: codexHomeLaunchStartedAt,
startedSequence: codexHomeLaunchStartedSequence,
...codexReattachedHomeRouteField(
reattachedCodexHomeRoutes,
result.id,
result.isReattach === true
),
...(result.isReattach === true
? { reattached: true }
: baseEnv