perf(native-chat): stop the transcript lookup walking the whole projects tree

Naming reads the persisted conversation name on every Claude acquisition, which
resolves the transcript path through walkSessionFiles. That collected EVERY
match across ~/.claude/projects before taking files[0]: measured here at 84-103ms
warm over 2665 directories and 5911 transcripts, competing with the attach it
runs alongside. Return at the first match, which is the only one either caller
reads and leaves traversal order untouched — 86ms to 0.75ms when the transcript
is found early, unchanged when it is found late or not at all.
This commit is contained in:
Merge Sim
2026-09-07 11:45:44 -07:00
parent 5e9c4e89ff
commit 75c6db2437
3 changed files with 63 additions and 0 deletions
@@ -0,0 +1,49 @@
import type { Dirent } from 'node:fs'
import { describe, expect, it, vi } from 'vitest'
import { walkSessionFiles } from './session-scanner-discovery'
function dirent(name: string, directory: boolean): Dirent {
return { name, isDirectory: () => directory, isFile: () => !directory } as Dirent
}
/** Two project directories, each holding the transcript the caller asked for. */
function tree(): Record<string, Dirent[]> {
return {
'/projects': [dirent('a', true), dirent('b', true)],
'/projects/a': [dirent('other.jsonl', false), dirent('target.jsonl', false)],
'/projects/b': [dirent('target.jsonl', false)]
}
}
describe('walkSessionFiles stopAfterFirstMatch', () => {
const options = (readDirectory: (path: string) => Promise<Dirent[]>) => ({
extensions: new Set(['.jsonl']),
filePredicate: (path: string) => path.endsWith('target.jsonl'),
readDirectory
})
it('stops walking once one file matches', async () => {
const entries = tree()
const readDirectory = vi.fn(async (path: string) => entries[path] ?? [])
const files = await walkSessionFiles('/projects', 'claude', [], {
...options(readDirectory),
stopAfterFirstMatch: true
})
expect(files).toEqual(['/projects/a/target.jsonl'])
// The whole point: the rest of the tree is never read. On a home with
// thousands of transcripts that traversal is most of the call.
expect(readDirectory.mock.calls.map((call) => call[0])).toEqual(['/projects', '/projects/a'])
})
it('returns the same file the exhaustive walk would have returned first', async () => {
const entries = tree()
const readDirectory = vi.fn(async (path: string) => entries[path] ?? [])
const all = await walkSessionFiles('/projects', 'claude', [], options(readDirectory))
expect(all[0]).toBe('/projects/a/target.jsonl')
expect(all).toHaveLength(2)
})
})
@@ -96,6 +96,11 @@ export async function walkSessionFiles(
// rootDir, so pruned subtrees are never stat'd or parsed.
directoryPredicate?: (name: string, depth: number) => boolean
readDirectory?: (dirPath: string) => Promise<Dirent[]>
/** Return as soon as one file matches. For a caller that only wants
* `files[0]`, walking the rest of the tree is pure cost — on a home with
* thousands of transcripts it is most of the call. Traversal order is
* unchanged, so the file returned is the same one. */
stopAfterFirstMatch?: boolean
signal?: AbortSignal
},
depth = 0
@@ -125,6 +130,9 @@ export async function walkSessionFiles(
// avoiding the readdir cost of descending into them.
if (options.directoryPredicate?.(entry.name, depth) ?? true) {
files.push(...(await walkSessionFiles(fullPath, agent, issues, options, depth + 1)))
if (options.stopAfterFirstMatch && files.length > 0) {
return files
}
}
continue
}
@@ -134,6 +142,9 @@ export async function walkSessionFiles(
(options.filePredicate?.(fullPath) ?? true)
) {
files.push(fullPath)
if (options.stopAfterFirstMatch) {
return files
}
}
}
return files
@@ -234,6 +234,8 @@ async function resolveClaudeSessionFile(
const files = await walkSessionFiles(projectsDir, 'claude', [], {
extensions: new Set(['.jsonl']),
filePredicate: (path) => basename(path) === targetName,
// Only `files[0]` is ever used, and this runs on every acquisition.
stopAfterFirstMatch: true,
signal
})
if (files[0]) {
@@ -291,6 +293,7 @@ async function findCodexRolloutInDirs(
: (
await walkSessionFiles(sessionsDir, 'codex', [], {
...scanOptions,
stopAfterFirstMatch: true,
signal
})
)[0]