Skip to content

Commit 286bcca

Browse files
Claudeclaude
authored andcommitted
perf: parallelize session_start queries and fire-and-forget transcript upload
session_start: sessions_lite and threads_lite queries now run in Promise.all instead of sequentially (~200-300ms saved). session_close: transcript upload moved from blocking await to fire-and-forget via effect tracker. Sync claude_session_id extraction preserved. Removes 500-5000ms variable cost from latency_ms. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8557bc3 commit 286bcca

File tree

2 files changed

+67
-14
lines changed

2 files changed

+67
-14
lines changed

src/tools/session-close.ts

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,30 @@ function countScarsApplied(scarsApplied: string | string[] | undefined | null):
7171
return normalizeScarsApplied(scarsApplied).length;
7272
}
7373

74+
/**
75+
* Find transcript file path: explicit param or auto-detect from Claude Code projects dir
76+
*/
77+
function findTranscriptPath(explicitPath?: string): string | null {
78+
if (explicitPath) {
79+
if (fs.existsSync(explicitPath)) {
80+
console.error(`[session_close] Using explicit transcript path: ${explicitPath}`);
81+
return explicitPath;
82+
}
83+
console.warn(`[session_close] Explicit transcript path does not exist: ${explicitPath}`);
84+
}
85+
const homeDir = os.homedir();
86+
const projectsDir = path.join(homeDir, ".claude", "projects");
87+
const cwd = process.cwd();
88+
const projectDirName = path.basename(cwd);
89+
const found = findMostRecentTranscript(projectsDir, projectDirName, cwd);
90+
if (found) {
91+
console.error(`[session_close] Auto-detected transcript: ${found}`);
92+
} else {
93+
console.error(`[session_close] No transcript file found in ${projectsDir}`);
94+
}
95+
return found;
96+
}
97+
7498
/**
7599
* Find the most recently modified transcript file in Claude Code projects directory
76100
* Search by recency, not by filename matching (supports post-compaction)
@@ -1198,15 +1222,43 @@ export async function sessionClose(
11981222
}
11991223

12001224
// Capture transcript if enabled (default true for CLI/DAC)
1225+
// Split into two phases: sync ID extraction (fast) + async upload (fire-and-forget)
12011226
let transcriptStatus: TranscriptStatus | undefined;
12021227
const shouldCaptureTranscript = params.capture_transcript !== false &&
12031228
(agentIdentity === "cli" || agentIdentity === "desktop");
12041229

12051230
if (shouldCaptureTranscript) {
1206-
const transcriptResult = await captureSessionTranscript(sessionId, params, existingSession, isRetroactive);
1207-
transcriptStatus = transcriptResult.status;
1208-
if (transcriptResult.claudeSessionId) {
1209-
sessionData.claude_code_session_id = transcriptResult.claudeSessionId;
1231+
// Phase 1: Find transcript and extract Claude session ID (sync, ~10ms)
1232+
const transcriptFilePath = findTranscriptPath(params.transcript_path);
1233+
if (transcriptFilePath) {
1234+
const transcriptContent = fs.readFileSync(transcriptFilePath, "utf-8");
1235+
const claudeSessionId = extractClaudeSessionId(transcriptContent, transcriptFilePath) || undefined;
1236+
if (claudeSessionId) {
1237+
sessionData.claude_code_session_id = claudeSessionId;
1238+
console.error(`[session_close] Extracted Claude session ID: ${claudeSessionId}`);
1239+
}
1240+
1241+
// Phase 2: Upload transcript (fire-and-forget — was blocking ~500-5000ms)
1242+
const transcriptProject = isRetroactive ? "default" : (existingSession?.project as string | undefined);
1243+
getEffectTracker().track("transcript", "session_close", async () => {
1244+
const saveResult = await saveTranscript({
1245+
session_id: sessionId,
1246+
transcript: transcriptContent,
1247+
format: "json",
1248+
project: transcriptProject,
1249+
});
1250+
if (saveResult.success && saveResult.transcript_path) {
1251+
console.error(`[session_close] Transcript saved: ${saveResult.transcript_path} (${saveResult.size_kb}KB)`);
1252+
// Process transcript for semantic search (chained fire-and-forget)
1253+
processTranscript(sessionId, transcriptContent, transcriptProject)
1254+
.then(result => {
1255+
if (result.success) {
1256+
console.error(`[session_close] Transcript processed: ${result.chunksCreated} chunks`);
1257+
}
1258+
})
1259+
.catch((err) => console.error("[session_close] Transcript processing failed:", err instanceof Error ? err.message : err));
1260+
}
1261+
});
12101262
}
12111263
}
12121264

src/tools/session-start.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -167,20 +167,21 @@ async function loadLastSession(
167167
}
168168

169169
try {
170-
// Use _lite view for performance (excludes embedding)
171-
// View now includes decisions/open_threads arrays
172-
const sessions = await supabase.listRecords<SessionRecord>({
173-
table: getTableName("sessions_lite"),
174-
filters: { agent, project },
175-
limit: 10, // Get several to find a closed one + aggregate threads
176-
orderBy: { column: "created_at", ascending: false },
177-
});
170+
// Parallel load: sessions + threads are independent queries
171+
// (was sequential — ~200-300ms saved by parallelizing)
172+
const [sessions, supabaseThreads] = await Promise.all([
173+
supabase.listRecords<SessionRecord>({
174+
table: getTableName("sessions_lite"),
175+
filters: { agent, project },
176+
limit: 10,
177+
orderBy: { column: "created_at", ascending: false },
178+
}),
179+
loadActiveThreadsFromSupabase(project),
180+
]);
178181

179-
// Try loading threads from Supabase (source of truth) first
180182
let aggregated_open_threads: ThreadObject[];
181183
let displayInfo: ThreadDisplayInfo[] = [];
182184
let threadsFromSupabase = false;
183-
const supabaseThreads = await loadActiveThreadsFromSupabase(project);
184185

185186
if (supabaseThreads !== null) {
186187
// Supabase is source of truth for threads

0 commit comments

Comments
 (0)