feat(codex): collapse duplicate session listings across Codex roots

Backfilled/bridged rollouts are hardlinked into both the real ~/.codex and
Orca's managed runtime home, so AI Vault listed each session once per root
(#7521). Dedup candidates by rollout file name pre-parse and parsed sessions
by session id post-parse, keeping the canonical root: host real home first
(unprefixed resume), then the managed runtime home, then other homes. Applies
to local, WSL, and SSH-remote scans.
This commit is contained in:
Brennan Benson
2026-07-15 17:33:59 -07:00
parent dda4c37a0d
commit 22dc271b91
10 changed files with 522 additions and 22 deletions
@@ -0,0 +1,186 @@
import { describe, expect, it } from 'vitest'
import type { AiVaultSession } from '../../shared/ai-vault-types'
import {
dedupeCodexRolloutFileAliases,
dedupeCodexSessionsBySessionId
} from './codex-session-root-dedup'
const REAL_HOME_ROLLOUT =
'/Users/ada/.codex/sessions/2026/07/01/rollout-2026-07-01T10-00-00-019f0000-1111-7222-8333-444444444444.jsonl'
const MANAGED_HOME_ROLLOUT =
'/Users/ada/Library/Application Support/orca/codex-runtime-home/home/sessions/2026/07/01/rollout-2026-07-01T10-00-00-019f0000-1111-7222-8333-444444444444.jsonl'
const MANAGED_HOME = '/Users/ada/Library/Application Support/orca/codex-runtime-home/home'
function codexSession(overrides: Partial<AiVaultSession>): AiVaultSession {
return {
id: `local:codex:${overrides.sessionId ?? 'session-1'}:${overrides.filePath ?? '/tmp/x.jsonl'}`,
executionHostId: 'local',
agent: 'codex',
sessionId: 'session-1',
title: 'Session',
cwd: '/repo/app',
branch: null,
model: null,
filePath: '/tmp/x.jsonl',
codexHome: null,
createdAt: '2026-07-01T10:00:00.000Z',
updatedAt: '2026-07-01T10:05:00.000Z',
modifiedAt: '2026-07-01T10:05:00.000Z',
messageCount: 1,
totalTokens: 10,
previewMessages: [],
queuedMessageCount: 0,
subagentTranscriptCount: 0,
resumeCommand: 'codex resume session-1',
subagent: null,
...overrides
} as AiVaultSession
}
describe('dedupeCodexRolloutFileAliases', () => {
type Candidate = { agent: string; path: string; codexHome: string | null }
const accessors = {
isCodex: (candidate: Candidate) => candidate.agent === 'codex',
getFilePath: (candidate: Candidate) => candidate.path,
getCodexHome: (candidate: Candidate) => candidate.codexHome
}
it('keeps the real-home alias when the same rollout exists in both roots', () => {
const managed = { agent: 'codex', path: MANAGED_HOME_ROLLOUT, codexHome: MANAGED_HOME }
const real = { agent: 'codex', path: REAL_HOME_ROLLOUT, codexHome: null }
expect(dedupeCodexRolloutFileAliases([managed, real], accessors)).toEqual([real])
expect(dedupeCodexRolloutFileAliases([real, managed], accessors)).toEqual([real])
})
it('prefers the managed runtime home over other non-default homes', () => {
const managed = { agent: 'codex', path: MANAGED_HOME_ROLLOUT, codexHome: MANAGED_HOME }
const wslReal = {
agent: 'codex',
path: `\\\\wsl$\\Ubuntu\\home\\ada\\.codex\\sessions\\2026\\07\\01\\rollout-2026-07-01T10-00-00-019f0000-1111-7222-8333-444444444444.jsonl`,
codexHome: '\\\\wsl$\\Ubuntu\\home\\ada\\.codex'
}
expect(dedupeCodexRolloutFileAliases([wslReal, managed], accessors)).toEqual([managed])
})
it('recognizes the managed runtime home with backslash separators', () => {
const managed = {
agent: 'codex',
path: 'C:\\Users\\ada\\AppData\\Roaming\\orca\\codex-runtime-home\\home\\sessions\\2026\\07\\01\\rollout-2026-07-01T10-00-00-019f0000-1111-7222-8333-444444444444.jsonl',
codexHome: 'C:\\Users\\ada\\AppData\\Roaming\\orca\\codex-runtime-home\\home'
}
const custom = {
agent: 'codex',
path: 'D:\\codex\\sessions\\2026\\07\\01\\rollout-2026-07-01T10-00-00-019f0000-1111-7222-8333-444444444444.jsonl',
codexHome: 'D:\\codex'
}
expect(dedupeCodexRolloutFileAliases([custom, managed], accessors)).toEqual([managed])
})
it('keeps distinct rollouts, non-codex candidates, and non-rollout file names', () => {
const real = { agent: 'codex', path: REAL_HOME_ROLLOUT, codexHome: null }
const other = {
agent: 'codex',
path: '/Users/ada/.codex/sessions/2026/07/02/rollout-2026-07-02T09-00-00-029f0000-1111-7222-8333-555555555555.jsonl',
codexHome: null
}
const oddName = {
agent: 'codex',
path: `${MANAGED_HOME}/sessions/notes.jsonl`,
codexHome: MANAGED_HOME
}
const claude = { agent: 'claude', path: REAL_HOME_ROLLOUT, codexHome: null }
expect(dedupeCodexRolloutFileAliases([real, other, oddName, claude], accessors)).toEqual([
real,
other,
oddName,
claude
])
})
})
describe('dedupeCodexSessionsBySessionId', () => {
it('collapses a both-roots session to the real-home row', () => {
const managed = codexSession({
filePath: MANAGED_HOME_ROLLOUT,
codexHome: MANAGED_HOME,
id: `local:codex:session-1:${MANAGED_HOME_ROLLOUT}`
})
const real = codexSession({
filePath: REAL_HOME_ROLLOUT,
codexHome: null,
id: `local:codex:session-1:${REAL_HOME_ROLLOUT}`
})
expect(dedupeCodexSessionsBySessionId([managed, real])).toEqual([real])
expect(dedupeCodexSessionsBySessionId([real, managed])).toEqual([real])
})
it('keeps managed-only and real-only sessions unchanged', () => {
const managedOnly = codexSession({
sessionId: 'managed-only',
filePath: `${MANAGED_HOME}/sessions/2026/07/01/rollout-a.jsonl`,
codexHome: MANAGED_HOME
})
const realOnly = codexSession({
sessionId: 'real-only',
filePath: REAL_HOME_ROLLOUT,
codexHome: null
})
expect(dedupeCodexSessionsBySessionId([managedOnly, realOnly])).toEqual([managedOnly, realOnly])
})
it('never collapses across execution hosts or agents', () => {
const local = codexSession({ sessionId: 'session-1', executionHostId: 'local' })
const remote = codexSession({
sessionId: 'session-1',
executionHostId: 'ssh:build-box',
id: 'ssh:build-box:codex:session-1:/home/ada/.codex/sessions/x.jsonl'
})
const claude = codexSession({ sessionId: 'session-1', agent: 'claude' })
expect(dedupeCodexSessionsBySessionId([local, remote, claude])).toEqual([local, remote, claude])
})
it('resolves same-rank id collisions to the newest row, then stable path order', () => {
const older = codexSession({
sessionId: 'collision',
filePath: '/Users/ada/.codex/sessions/2026/07/01/rollout-old.jsonl',
codexHome: null,
updatedAt: '2026-07-01T10:00:00.000Z',
modifiedAt: '2026-07-01T10:00:00.000Z'
})
const newer = codexSession({
sessionId: 'collision',
filePath: '/Users/ada/.codex/sessions/2026/07/02/rollout-new.jsonl',
codexHome: null,
updatedAt: '2026-07-02T10:00:00.000Z',
modifiedAt: '2026-07-02T10:00:00.000Z'
})
expect(dedupeCodexSessionsBySessionId([older, newer])).toEqual([newer])
const tieA = codexSession({
sessionId: 'tie',
filePath: '/Users/ada/.codex/sessions/2026/07/01/rollout-a.jsonl',
codexHome: null
})
const tieB = codexSession({
sessionId: 'tie',
filePath: '/Users/ada/.codex/sessions/2026/07/01/rollout-b.jsonl',
codexHome: null
})
expect(dedupeCodexSessionsBySessionId([tieB, tieA])).toEqual([tieA])
})
it('prefers the managed runtime home over a WSL real home when no host real-home row exists', () => {
const wslManaged = codexSession({
sessionId: 'wsl-pair',
filePath:
'\\\\wsl$\\Ubuntu\\home\\ada\\.local\\share\\orca\\codex-runtime-home\\home\\sessions\\rollout-a.jsonl',
codexHome: '\\\\wsl$\\Ubuntu\\home\\ada\\.local\\share\\orca\\codex-runtime-home\\home'
})
const wslReal = codexSession({
sessionId: 'wsl-pair',
filePath: '\\\\wsl$\\Ubuntu\\home\\ada\\.codex\\sessions\\rollout-a.jsonl',
codexHome: '\\\\wsl$\\Ubuntu\\home\\ada\\.codex'
})
expect(dedupeCodexSessionsBySessionId([wslReal, wslManaged])).toEqual([wslManaged])
})
})
@@ -0,0 +1,116 @@
import type { AiVaultSession } from '../../shared/ai-vault-types'
import { sessionSortTime } from './session-scanner-accumulator'
// Why: the session bridge and the real-home backfill hardlink one physical
// Codex rollout into multiple scanned roots (managed runtime home and the
// user's own ~/.codex), so every bridged/backfilled session used to list once
// per root (#7521). These helpers collapse those aliases to one canonical row.
// Matches Codex rollout logs: rollout-<timestamp>-<session uuid>.jsonl. The
// bridge and backfill both preserve the sessions/YYYY/MM/DD layout, so an
// identical rollout file name across Codex roots is the same session.
const CODEX_ROLLOUT_FILE_NAME_PATTERN = /^rollout-.+\.jsonl$/
// Why: not node:path.basename — a posix host scans remote/WSL win32 paths, so
// separators must be handled independently of the local platform.
function lastPathSegment(filePath: string): string {
return filePath.split(/[\\/]/).at(-1) ?? ''
}
/**
* Ranks a Codex session root for canonical-alias selection, lowest wins.
*
* Host real home (null) is canonical: after the real-home flip the managed
* home's auth.json is no longer refreshed, so resume must not stamp it. The
* Orca managed runtime home still beats other homes (WSL/remote real homes,
* custom CODEX_HOMEs) because those lanes have not flipped — their launches
* keep managed auth, so resume keeps the managed stamp as today.
*/
function codexSessionRootRank(codexHome: string | null): number {
if (codexHome === null) {
return 0
}
const segments = codexHome.split(/[\\/]/).filter(Boolean)
return segments.at(-2) === 'codex-runtime-home' && segments.at(-1) === 'home' ? 1 : 2
}
/**
* Drops pre-parse Codex rollout candidates that alias an already-kept rollout
* file name in a preferred root, so duplicate aliases never consume the parse
* budget or crowd the capped listing.
*/
export function dedupeCodexRolloutFileAliases<T>(
candidates: readonly T[],
accessors: {
isCodex: (candidate: T) => boolean
getFilePath: (candidate: T) => string
getCodexHome: (candidate: T) => string | null
}
): T[] {
const bestByFileName = new Map<string, { candidate: T; rank: number; filePath: string }>()
for (const candidate of candidates) {
if (!accessors.isCodex(candidate)) {
continue
}
const filePath = accessors.getFilePath(candidate)
const fileName = lastPathSegment(filePath)
if (!CODEX_ROLLOUT_FILE_NAME_PATTERN.test(fileName)) {
continue
}
const rank = codexSessionRootRank(accessors.getCodexHome(candidate))
const best = bestByFileName.get(fileName)
if (!best || rank < best.rank || (rank === best.rank && filePath < best.filePath)) {
bestByFileName.set(fileName, { candidate, rank, filePath })
}
}
return candidates.filter((candidate) => {
if (!accessors.isCodex(candidate)) {
return true
}
const fileName = lastPathSegment(accessors.getFilePath(candidate))
const best = bestByFileName.get(fileName)
return !best || best.candidate === candidate
})
}
/**
* Collapses parsed Codex sessions that share a session id on one execution
* host, keeping the canonical root's row (see codexSessionRootRank). Catches
* aliases the file-name pass cannot see: cross-volume backfill copies and
* session_meta ids that differ from the rollout file name.
*/
export function dedupeCodexSessionsBySessionId(
sessions: readonly AiVaultSession[]
): AiVaultSession[] {
const bestByKey = new Map<string, AiVaultSession>()
for (const session of sessions) {
if (session.agent !== 'codex') {
continue
}
const key = `${session.executionHostId}:${session.sessionId}`
const best = bestByKey.get(key)
if (!best || codexSessionAliasBeats(session, best)) {
bestByKey.set(key, session)
}
}
return sessions.filter((session) => {
if (session.agent !== 'codex') {
return true
}
return bestByKey.get(`${session.executionHostId}:${session.sessionId}`) === session
})
}
function codexSessionAliasBeats(candidate: AiVaultSession, best: AiVaultSession): boolean {
const candidateRank = codexSessionRootRank(candidate.codexHome)
const bestRank = codexSessionRootRank(best.codexHome)
if (candidateRank !== bestRank) {
return candidateRank < bestRank
}
const candidateTime = sessionSortTime(candidate)
const bestTime = sessionSortTime(best)
if (candidateTime !== bestTime) {
return candidateTime > bestTime
}
return candidate.filePath < best.filePath
}
@@ -156,6 +156,7 @@ function remoteCodexSources(
].map((codexHome) => ({
agent: 'codex',
rootDir: joinRemotePath(hostPlatform, codexHome, 'sessions'),
codexHome,
extensions: ['.jsonl'],
parse: (file, content, context) =>
parseCodexSessionContent({
@@ -19,6 +19,9 @@ export type RemoteParserOptions = {
export type RemoteSessionSource = {
agent: AiVaultAgent
rootDir: string
// Codex sources only: the CODEX_HOME the root belongs to, so bridged or
// backfilled rollout aliases across remote roots collapse to one canonical row.
codexHome?: string
extensions: readonly string[]
filePredicate?: (path: string) => boolean
// Claude layout: count `<session>/subagents/*.jsonl` siblings from the walked
@@ -160,6 +160,40 @@ describe('scanRemoteAiVaultSessions', () => {
})
})
it('collapses a bridged rollout present in both remote Codex homes to one row', async () => {
const provider = new MemoryRemoteProvider()
const rolloutName = 'rollout-2026-07-04T10-00-00-019f0000-1111-7222-8333-444444444444.jsonl'
const transcript = codexTranscript({
sessionId: '019f0000-1111-7222-8333-444444444444',
title: 'Bridged both-homes session',
cwd: '/home/ada/repo',
timestamp: '2026-07-04T10:00:00.000Z'
})
// Same rollout name in both homes — the in-distro bridge/backfill hardlink.
provider.addFile(`/home/ada/.codex/sessions/2026/07/04/${rolloutName}`, transcript, 3_000)
provider.addFile(
`/home/ada/.local/share/orca/codex-runtime-home/home/sessions/2026/07/04/${rolloutName}`,
transcript,
3_000
)
const result = await scanRemoteAiVaultSessions({
provider,
executionHostId: 'ssh:build-box',
remoteHome: '/home/ada',
hostPlatform: getRemoteHostPlatform('linux-x64')
})
expect(result.issues).toEqual([])
expect(result.sessions).toHaveLength(1)
// Remote lanes have not flipped to the real home: the managed runtime-home
// row stays canonical so resume keeps Orca-refreshed auth, as today.
expect(result.sessions[0]).toMatchObject({
sessionId: '019f0000-1111-7222-8333-444444444444',
codexHome: '/home/ada/.local/share/orca/codex-runtime-home/home'
})
})
it('parses non-Codex transcripts through the same remote scanner', async () => {
const provider = new MemoryRemoteProvider()
provider.addFile(
+20 -8
View File
@@ -11,6 +11,10 @@ import type { FileStat, IFilesystemProvider } from '../providers/types'
import type { RemoteHostPlatform } from '../ssh/ssh-remote-platform'
import { joinRemotePath } from '../ssh/ssh-remote-platform'
import { sessionSortTime } from './session-scanner-accumulator'
import {
dedupeCodexRolloutFileAliases,
dedupeCodexSessionsBySessionId
} from './codex-session-root-dedup'
import { partitionSubagentTranscriptPaths } from './session-scanner-subagent-transcripts'
import type { FileWithMtime } from './session-scanner-types'
import { errorMessage } from './session-scanner-values'
@@ -41,21 +45,29 @@ export async function scanRemoteAiVaultSessions(args: {
hostPlatform: args.hostPlatform,
titleCaches: new Map()
}
const candidates = (
await mapRemoteScanConcurrently(
remoteSessionSources(args.remoteHome, args.hostPlatform),
(source) => discoverRemoteSourceCandidates({ source, context, issues })
const candidates = dedupeCodexRolloutFileAliases(
(
await mapRemoteScanConcurrently(
remoteSessionSources(args.remoteHome, args.hostPlatform),
(source) => discoverRemoteSourceCandidates({ source, context, issues })
)
)
.flat()
.sort((left, right) => right.file.mtimeMs - left.file.mtimeMs),
{
isCodex: (candidate) => candidate.source.agent === 'codex',
getFilePath: (candidate) => candidate.file.path,
getCodexHome: (candidate) => candidate.source.codexHome ?? null
}
)
.flat()
.sort((left, right) => right.file.mtimeMs - left.file.mtimeMs)
const parsed = await parseRemoteSessionCandidates({ candidates, context, issues, limit })
const cappedSessions = parsed.sessions
const parsedSessions = dedupeCodexSessionsBySessionId(parsed.sessions)
const cappedSessions = parsedSessions
.sort((left, right) => sessionSortTime(right) - sessionSortTime(left))
.slice(0, limit)
const scopePaths = normalizeRemoteScopePaths(args.scopePaths ?? [])
const parsedScopeSessions = parsed.sessions.filter((session) =>
const parsedScopeSessions = parsedSessions.filter((session) =>
isRemoteSessionInScope(session, scopePaths)
)
const extraScopeSessions = await scanRemoteInScopeSessions({
@@ -0,0 +1,116 @@
import { link, mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { scanAiVaultSessions } from './session-scanner'
import { isolatedScanRoots, jsonLines } from './session-scanner-test-fixtures'
// Scan-level coverage for the canonical-root rule when one physical Codex
// rollout is visible through both the real ~/.codex and the managed runtime
// home (the layout the session backfill and bridge produce via hardlinks).
let tempRoots: string[] = []
afterEach(async () => {
await Promise.all(tempRoots.map((root) => rm(root, { recursive: true, force: true })))
tempRoots = []
})
describe('scanAiVaultSessions codex dual-root dedup', () => {
it('lists a backfilled both-roots session once, attributed to the real home', async () => {
const root = await mkdtemp(join(tmpdir(), 'orca-ai-vault-codex-dedup-'))
tempRoots.push(root)
const roots = isolatedScanRoots(root)
// Sandbox "real ~/.codex" and Orca managed runtime home, hardlinked the
// same way the session backfill links managed rollouts into the real home.
const realHome = join(root, 'real-codex-home')
const realSessionsDir = join(realHome, 'sessions')
const managedHome = join(root, 'codex-runtime-home', 'home')
const managedSessionsDir = join(managedHome, 'sessions')
const rolloutName = 'rollout-2026-07-01T10-00-00-019f0000-1111-7222-8333-444444444444.jsonl'
await mkdir(join(managedSessionsDir, '2026', '07', '01'), { recursive: true })
await mkdir(join(realSessionsDir, '2026', '07', '01'), { recursive: true })
await writeFile(
join(managedSessionsDir, '2026', '07', '01', rolloutName),
jsonLines([
{
timestamp: '2026-07-01T10:00:00.000Z',
type: 'session_meta',
payload: { id: '019f0000-1111-7222-8333-444444444444', cwd: '/repo/app' }
},
{
timestamp: '2026-07-01T10:00:01.000Z',
type: 'response_item',
payload: {
type: 'message',
role: 'user',
content: [{ type: 'text', text: 'Backfilled both-roots session' }]
}
}
])
)
await link(
join(managedSessionsDir, '2026', '07', '01', rolloutName),
join(realSessionsDir, '2026', '07', '01', rolloutName)
)
// A managed-only session (e.g. a backfill copy failure) must keep its
// managed-home stamp so resume still targets the home that has it.
await mkdir(join(managedSessionsDir, '2026', '07', '02'), { recursive: true })
await writeFile(
join(
managedSessionsDir,
'2026',
'07',
'02',
'rollout-2026-07-02T09-00-00-029f0000-1111-7222-8333-555555555555.jsonl'
),
jsonLines([
{
timestamp: '2026-07-02T09:00:00.000Z',
type: 'session_meta',
payload: { id: '029f0000-1111-7222-8333-555555555555', cwd: '/repo/app' }
},
{
timestamp: '2026-07-02T09:00:01.000Z',
type: 'response_item',
payload: {
type: 'message',
role: 'user',
content: [{ type: 'text', text: 'Managed-only session' }]
}
}
])
)
const result = await scanAiVaultSessions({
...roots,
codexSessionsDir: realSessionsDir,
defaultCodexHomeDir: realHome,
additionalCodexSessionsDirs: [managedSessionsDir],
platform: 'darwin'
})
expect(result.issues).toEqual([])
const codexSessions = result.sessions.filter((session) => session.agent === 'codex')
expect(codexSessions).toHaveLength(2)
const backfilled = codexSessions.find(
(session) => session.sessionId === '019f0000-1111-7222-8333-444444444444'
)
expect(backfilled).toMatchObject({
codexHome: null,
filePath: join(realSessionsDir, '2026', '07', '01', rolloutName),
resumeCommand: "cd '/repo/app' && codex resume '019f0000-1111-7222-8333-444444444444'"
})
expect(backfilled?.resumeCommand).not.toContain('CODEX_HOME')
const managedOnly = codexSessions.find(
(session) => session.sessionId === '029f0000-1111-7222-8333-555555555555'
)
expect(managedOnly).toMatchObject({
codexHome: managedHome,
resumeCommand: `cd '/repo/app' && CODEX_HOME='${managedHome}' codex resume '029f0000-1111-7222-8333-555555555555'`
})
})
})
@@ -10,6 +10,9 @@ export type AiVaultScanOptions = {
claudeProjectsDir?: string
codexSessionsDir?: string
additionalCodexSessionsDirs?: readonly string[]
// Why: tests inject a sandbox "real ~/.codex" so real-home attribution
// (codexHome null → unprefixed resume) is testable without the user's home.
defaultCodexHomeDir?: string
wslHomeDirs?: readonly string[]
geminiSessionsDir?: string
copilotSessionsDir?: string
+28 -14
View File
@@ -6,6 +6,10 @@ import type {
import { LOCAL_EXECUTION_HOST_ID, type ExecutionHostId } from '../../shared/execution-host'
import { withSpan } from '../observability/tracer'
import { sessionSortTime } from './session-scanner-accumulator'
import {
dedupeCodexRolloutFileAliases,
dedupeCodexSessionsBySessionId
} from './codex-session-root-dedup'
import { codexHomeForSessionsDir } from './session-scanner-codex-paths'
import {
createSessionParseStats,
@@ -56,20 +60,30 @@ export async function scanAiVaultSessions(
const parseStats = createSessionParseStats()
const discoveries = await discoverAiVaultSessionSources({ options, limitPerAgent, issues })
const candidates = discoveries
.flatMap((discovery) =>
discovery.files.map(
(file): SessionFileCandidate => ({
agent: discovery.agent,
file,
codexHome:
discovery.agent === 'codex'
? codexHomeForSessionsDir(discovery.rootDir, DEFAULT_CODEX_HOME_DIR)
: null
})
const candidates = dedupeCodexRolloutFileAliases(
discoveries
.flatMap((discovery) =>
discovery.files.map(
(file): SessionFileCandidate => ({
agent: discovery.agent,
file,
codexHome:
discovery.agent === 'codex'
? codexHomeForSessionsDir(
discovery.rootDir,
options.defaultCodexHomeDir ?? DEFAULT_CODEX_HOME_DIR
)
: null
})
)
)
)
.sort((left, right) => right.file.mtimeMs - left.file.mtimeMs)
.sort((left, right) => right.file.mtimeMs - left.file.mtimeMs),
{
isCodex: (candidate) => candidate.agent === 'codex',
getFilePath: (candidate) => candidate.file.path,
getCodexHome: (candidate) => candidate.codexHome
}
)
const parsedSessions = await parseSessionCandidates({
candidates,
@@ -80,7 +94,7 @@ export async function scanAiVaultSessions(
parseStats
})
const cappedSessions = parsedSessions
const cappedSessions = dedupeCodexSessionsBySessionId(parsedSessions)
.sort((left, right) => sessionSortTime(right) - sessionSortTime(left))
.slice(0, limit)
@@ -29,6 +29,21 @@ describe('buildAiVaultResumeCommand', () => {
)
})
it('emits no CODEX_HOME stamp for real-home canonical sessions', () => {
// Backfilled sessions dedupe to the real-home row (codexHome null); their
// resume must run against the user's own ~/.codex, never the frozen
// managed home whose auth.json stops refreshing after the flip.
const command = buildAiVaultResumeCommand({
agent: 'codex',
sessionId: 'session-1',
cwd: '/repo/app',
platform: 'darwin',
codexHome: null
})
expect(command).toBe("cd '/repo/app' && codex resume 'session-1'")
expect(command).not.toContain('CODEX_HOME')
})
it('carries non-default Codex homes in copied resume commands', () => {
expect(
buildAiVaultResumeCommand({