Files
orca/src/main/codex/codex-session-backfill-date.ts
T
Neil 015f904fca fix(codex): stop re-scanning all Codex session history on every launch (#16251) (#16593)
* fix(codex): stop re-scanning all Codex session history on every launch (#16251)

A launch deleted the backfill completion marker, and a marker could never
be written while a Codex pane was open, so every launch re-derived
"needs full scan" and walked the entire .codex/sessions tree — on Windows
with a large history that read as a hung window.

- v4 marker keeps a durable full-history baseline plus a bounded set of
  pending dates. v3 is read as a baseline, so upgrades pay no full scan.
- A launch now marks dates pending instead of deleting the marker, and a
  full pass certifies the baseline even while a pane is still running;
  the live pane's own date just stays pending.
- Pending dates are persisted, so an abnormal exit or a cross-midnight
  pane recovers a bounded window instead of a full walk.
- A date-limited pass can only extend an existing baseline, never create
  one, so it can no longer certify history it never looked at.
- Marker and index-heal target roots compare through
  normalizeRuntimePathForComparison, so Windows spellings of one
  directory stop invalidating each other.
- Both append-only ledgers stream instead of readFileSync + whole-file
  JSON.parse, keeping the main thread responsive on large histories.

* fix(codex): keep the backfill marker's full-scan demand durable

Review follow-ups on the v4 backfill marker:

- markCodexSessionBackfillMarkerPending no longer erases a persisted
  needsFullScan; the demand survives until a generation-current full walk
  retires it, and the function now reports it so the launch path folds it
  into its own in-memory flag (as @rumoii's #16252 does).
- A full pass settles the whole pending set instead of subtracting the
  empty set, so a date a full walk provably covered stops forcing an extra
  bounded pass on every startup.
- isCodexSessionBackfillDate does a real calendar check, so a corrupted
  marker cannot carry 2026/99/99. No age or future bound: the same guard
  gates rollout publication and a clock-skewed directory holds real
  sessions.
- 'scans only the current date once a baseline exists' now has a second
  date directory, so it fails on a full walk instead of passing either way.
2026-08-26 15:42:54 -07:00

52 lines
1.7 KiB
TypeScript

import { join, relative, sep } from 'node:path'
import { isCodexSessionBackfillDate } from './codex-session-backfill-scan-dates'
import { listCodexSessionJsonlFilesIncrementally } from './codex-session-file-listing'
import type {
CodexSessionBackfillDate,
CodexSessionBackfillOptions
} from './codex-session-backfill-types'
export function isCodexSessionRolloutPath(sessionsRoot: string, filePath: string): boolean {
const pathParts = relative(sessionsRoot, filePath).split(sep)
if (pathParts.length !== 4) {
return false
}
const [year, month, day, fileName] = pathParts
return isCodexSessionBackfillDate([year, month, day]) && /^rollout-.+\.jsonl$/.test(fileName)
}
export async function* listCodexSessionBackfillFilesForDates(
sessionsRoot: string,
options: CodexSessionBackfillOptions,
onDirectoryError: (directoryPath: string, error: unknown) => void | Promise<void>
): AsyncGenerator<string> {
const scanRoots = resolveCodexSessionBackfillDateRoots(sessionsRoot, options.scanDates)
for (const scanRoot of scanRoots) {
yield* listCodexSessionJsonlFilesIncrementally(
scanRoot,
options,
async (directoryPath, error) => {
if (directoryPath !== scanRoot || !isNotFoundError(error)) {
await onDirectoryError(directoryPath, error)
}
}
)
}
}
function resolveCodexSessionBackfillDateRoots(
sessionsRoot: string,
scanDates: readonly CodexSessionBackfillDate[] | undefined
): string[] {
if (!scanDates?.length) {
return [sessionsRoot]
}
return scanDates
.filter(isCodexSessionBackfillDate)
.map(([year, month, day]) => join(sessionsRoot, year, month, day))
}
function isNotFoundError(error: unknown): boolean {
return (error as NodeJS.ErrnoException | null)?.code === 'ENOENT'
}