From 09d631f511f75a198486783d41d516e4f3f2ae4a Mon Sep 17 00:00:00 2001 From: Prateek Date: Wed, 18 Feb 2026 05:49:51 +0530 Subject: [PATCH] fix: deduplicate config/session lookups in send, hoist sm.list() in batch-spawn 1. Send command: merge resolveTmuxTarget() and resolveAgentForSession() into a single resolveSessionContext() that loads config and calls sm.get() once instead of twice. 2. Batch-spawn: move sm.list() call before the loop and build a Map for O(1) duplicate lookups, avoiding repeated metadata reads + runtime enrichment on every iteration. Co-Authored-By: Claude Sonnet 4.6 --- packages/cli/src/commands/send.ts | 39 +++++++++--------------------- packages/cli/src/commands/spawn.ts | 21 ++++++++++------ 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/packages/cli/src/commands/send.ts b/packages/cli/src/commands/send.ts index adf1fe935..e094862bd 100644 --- a/packages/cli/src/commands/send.ts +++ b/packages/cli/src/commands/send.ts @@ -9,41 +9,26 @@ import { getAgentByName } from "../lib/plugins.js"; import { getSessionManager } from "../lib/create-session-manager.js"; /** - * Resolve the tmux session name for a user-facing session ID. - * Returns the runtimeHandle.id (which is the tmux session name) or falls back - * to the session name itself for backwards compatibility with pre-hash sessions. + * Resolve session context: tmux target name and Agent plugin. + * Loads config and looks up the session once, avoiding duplicate work. */ -async function resolveTmuxTarget(sessionName: string): Promise { - try { - const config = loadConfig(); - const sm = await getSessionManager(config); - const session = await sm.get(sessionName); - if (session?.runtimeHandle?.id) { - return session.runtimeHandle.id; - } - } catch { - // No config or session not found — fall back to session name as tmux name - } - return sessionName; -} - -/** - * Resolve the Agent plugin for a session. - */ -async function resolveAgentForSession(sessionName: string): Promise<{ agent: Agent; session: Session | null }> { +async function resolveSessionContext( + sessionName: string, +): Promise<{ tmuxTarget: string; agent: Agent; session: Session | null }> { try { const config = loadConfig(); const sm = await getSessionManager(config); const session = await sm.get(sessionName); if (session) { + const tmuxTarget = session.runtimeHandle?.id ?? sessionName; const project = config.projects[session.projectId]; const agentName = project?.agent ?? config.defaults.agent; - return { agent: getAgentByName(agentName), session }; + return { tmuxTarget, agent: getAgentByName(agentName), session }; } } catch { - // Fall through to default + // No config or session not found — fall back to defaults } - return { agent: getAgentByName("claude-code"), session: null }; + return { tmuxTarget: sessionName, agent: getAgentByName("claude-code"), session: null }; } function isActive(agent: Agent, terminalOutput: string): boolean { @@ -73,8 +58,8 @@ export function registerSend(program: Command): void { messageParts: string[], opts: { file?: string; wait?: boolean; timeout?: string }, ) => { - // Resolve tmux target name (hash-based or legacy) - const tmuxTarget = await resolveTmuxTarget(session); + // Resolve session context once: tmux target, agent plugin, session data + const { tmuxTarget, agent } = await resolveSessionContext(session); const exists = await tmux("has-session", "-t", tmuxTarget); if (exists === null) { @@ -89,8 +74,6 @@ export function registerSend(program: Command): void { process.exit(1); } - const { agent } = await resolveAgentForSession(session); - const parsedTimeout = parseInt(opts.timeout || "600", 10); const timeoutMs = (isNaN(parsedTimeout) || parsedTimeout <= 0 ? 600 : parsedTimeout) * 1000; diff --git a/packages/cli/src/commands/spawn.ts b/packages/cli/src/commands/spawn.ts index fb92450b4..57a0fd298 100644 --- a/packages/cli/src/commands/spawn.ts +++ b/packages/cli/src/commands/spawn.ts @@ -111,6 +111,14 @@ export function registerBatchSpawn(program: Command): void { const failed: Array<{ issue: string; error: string }> = []; const spawnedIssues = new Set(); + // Load existing sessions once before the loop to avoid repeated reads + enrichment + const existingSessions = await sm.list(projectId); + const existingIssueMap = new Map( + existingSessions + .filter((s) => s.issueId) + .map((s) => [s.issueId!.toLowerCase(), s.id]), + ); + for (const issue of issues) { // Duplicate detection — check both existing sessions and same-run duplicates if (spawnedIssues.has(issue.toLowerCase())) { @@ -119,14 +127,11 @@ export function registerBatchSpawn(program: Command): void { continue; } - // Check existing sessions via session manager (works with hash-based naming) - const existingSessions = await sm.list(projectId); - const existingSession = existingSessions.find( - (s) => s.issueId?.toLowerCase() === issue.toLowerCase(), - ); - if (existingSession) { - console.log(chalk.yellow(` Skip ${issue} — already has session: ${existingSession.id}`)); - skipped.push({ issue, existing: existingSession.id }); + // Check existing sessions (pre-loaded before loop) + const existingSessionId = existingIssueMap.get(issue.toLowerCase()); + if (existingSessionId) { + console.log(chalk.yellow(` Skip ${issue} — already has session: ${existingSessionId}`)); + skipped.push({ issue, existing: existingSessionId }); continue; }