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 <noreply@anthropic.com>
This commit is contained in:
Prateek 2026-02-18 05:49:51 +05:30
parent a5a98cd7aa
commit 09d631f511
2 changed files with 24 additions and 36 deletions

View File

@ -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<string> {
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;

View File

@ -111,6 +111,14 @@ export function registerBatchSpawn(program: Command): void {
const failed: Array<{ issue: string; error: string }> = [];
const spawnedIssues = new Set<string>();
// 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;
}