Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fix: scope session-event indexing to the current project (#1214)
maybeIndexSessionEvents glob-scanned every `*-events.md` in the shared
sessions directory, indexed them all into whichever project's store
happened to be open, then unlinked them. On a machine running several
projects that means one project's session events land in another
project's context, and the rightful project never gets them because the
file is already gone.

The old comment blamed CLAUDE_PROJECT_DIR being unavailable to MCP
servers. That is true of the env var but misleading about the server:
`getProjectDir()` is exported from this same file with a full resolution
chain, and `getStorePath()` already calls it to hash the content DB path.

The events filename already carries the project hash
(`<canonicalHash><suffix>-events.md`), so this resolves that one path via
`resolveSessionPath` — the project's own generalised resolver that also
powers `.db` and `.cleanup` — instead of globbing. Reusing it means the
JS hooks and the TS server cannot drift on hash, suffix or migration
policy. One existsSync also costs less than the old readdir.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
  • Loading branch information
ayojeges and claude committed Sep 30, 2026
commit 61808851f8cfa5e2f814e58de96c8cb8feff0bdd
43 changes: 29 additions & 14 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
resolveContentStorageDir,
resolveDefaultSessionDir,
resolveSessionDbPath,
resolveSessionPath,
resolveSessionStorageDir,
resolveStatsStorageDir,
SessionDB,
Expand Down Expand Up @@ -499,25 +500,39 @@ export function resolveSessionIdFromSessionDB(opts?: {
}

/**
* Auto-index session events files written by SessionStart hook.
* Scans ~/.claude/context-mode/sessions/ for *-events.md files.
* CLAUDE_PROJECT_DIR is NOT available to MCP servers — only to hooks —
* so we glob-scan instead of computing a specific hash.
* Files are consumed (deleted) after indexing to prevent double-indexing.
* Called on every getStore() — readdirSync is sub-millisecond when no files match.
* Auto-index the session events file written by the SessionStart hook.
*
* Scoped to THIS project only (issue #1214). The previous implementation
* glob-scanned every `*-events.md` in the shared sessions directory, indexed
* them all into whichever project's store happened to be open, and unlinked
* them. On a machine running several projects that meant one project's session
* events were indexed into another project's context, and the rightful project
* never received them because the file had already been consumed.
*
* The old comment said CLAUDE_PROJECT_DIR is unavailable to MCP servers. That
* is true of the env var but not of the server: `getProjectDir()` below has a
* full resolution chain, and `getStorePath()` already uses it to hash the
* content DB path. The events filename carries the same project hash, so
* `resolveSessionPath` (the generalised resolver that also powers `.db` and
* `.cleanup`) gives us exactly one path, and hooks and server cannot drift.
*
* The file is consumed (deleted) after indexing to prevent double-indexing.
* Called on every getStore(); one existsSync is cheaper than the old readdir.
*/
function maybeIndexSessionEvents(store: ContentStore): void {
try {
const sessionsDir = getSessionDir();
if (!existsSync(sessionsDir)) return;
const files = readdirSync(sessionsDir).filter(f => f.endsWith("-events.md"));
for (const file of files) {
const filePath = join(sessionsDir, file);
try {
store.index({ path: filePath, source: "session-events", attribution: currentAttribution() });
unlinkSync(filePath);
} catch { /* best-effort per file */ }
}
const filePath = resolveSessionPath({
projectDir: getProjectDir(),
sessionsDir,
ext: "-events.md",
});
if (!existsSync(filePath)) return;
try {
store.index({ path: filePath, source: "session-events", attribution: currentAttribution() });
unlinkSync(filePath);
} catch { /* best-effort — a bad events file never blocks tools */ }
} catch { /* best-effort — session continuity never blocks tools */ }
}

Expand Down