Skip to content

ContextSearch only ever searches one project dir — every session run inside a code repo is unrecallable #1494

Description

@christauff

Version: v7.1.1 (verified against 03a969dd96dc7ff701d92dbc8a8b8d6db4728834)

What happens

ContextSearch builds its transcript-search root from ~/.claude alone, so it only ever looks inside the single project dir that corresponds to ~/.claude. Claude Code writes one project dir per cwd. Any session run in any other directory — i.e. every session run inside a code repo — is invisible to recall, by construction.

Repro (30 seconds)

ls ~/.claude/projects/          # one dir per cwd you've worked in

On this machine that lists 11 dirs. ContextSearch searches exactly one of them.

Run a memorable session inside any code repo, then try to recall it with ContextSearch. It will not be found — not because the ranking missed it, but because the file was never a candidate.

Cause

const LIFEOS_DIR = join(HOME, ".claude");
const STATE_DIR = join(LIFEOS_DIR, "LIFEOS", "MEMORY", "STATE");
const WORK_DIR = join(LIFEOS_DIR, "LIFEOS", "MEMORY", "WORK");
// Claude Code names each project dir by its absolute path with "/" and "." mapped to "-",
// e.g. $HOME/.claude -> "-Users-<user>--claude". Derive it from $HOME instead of hardcoding.
const PROJECT_SLUG = LIFEOS_DIR.replace(/[/.]/g, "-");
const JSONL_DIR = join(LIFEOS_DIR, "Projects", PROJECT_SLUG);

const LIFEOS_DIR = join(HOME, ".claude");
const PROJECT_SLUG = LIFEOS_DIR.replace(/[/.]/g, "-");
const JSONL_DIR = join(LIFEOS_DIR, "Projects", PROJECT_SLUG);

JSONL_DIR is pinned to the ~/.claude slug. The containment guard in searchJsonl then re-asserts that same single-slug root:

if (tokens.length === 0 || !existsSync(JSONL_DIR)) return [];
const realDir = JSONL_DIR;
if (!realDir.startsWith(join(HOME, ".claude", "Projects", PROJECT_SLUG))) return [];

There is a second, independent consequence at the UUID join:

const jsonlPath = join(JSONL_DIR, `${uuid}.jsonl`);

const jsonlPath = join(JSONL_DIR, `${uuid}.jsonl`);

A transcript actually lives at projects/<cwd-slug>/<uuid>.jsonl, and the slug varies by the cwd the session ran in — so a UUID cannot be joined onto one slug. Once the root is corrected this join must resolve across project dirs, or it breaks.

Fix

Point at the projects root and let ripgrep recurse; resolve UUIDs across project dirs.

const JSONL_DIR = join(HOME, ".claude", "projects");

function findTranscriptByUuid(uuid: string): string | null {
  try {
    for (const d of readdirSync(JSONL_DIR, { withFileTypes: true })) {
      if (!d.isDirectory()) continue;
      const p = join(JSONL_DIR, d.name, `${uuid}.jsonl`);
      if (existsSync(p)) return p;
    }
  } catch {}
  return null;
}
  • searchJsonl containment guard becomes realDir.startsWith(join(HOME, ".claude", "projects")) — still contained, now to all cwds rather than one slug.
  • The :243 join becomes findTranscriptByUuid(uuid).

Separately, searchIsaBodies globs *ISA.md only. Native sessions produce a SESSION.md durable record, so the artifact layer indexes Algorithm-mode sessions exclusively. Adding --glob *SESSION.md makes native work findable. Happy to split that into its own issue if you'd rather keep this one to the scope fix.

I have this working locally and will open a PR against main if you want it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions