fix: address resource leaks and undefined env variable

Fixed 4 Bugbot issues:

1. HIGH: Undefined $CLAUDE_PROJECT_DIR in hook script path
   - Changed setupWorkspaceHooks to use absolute path instead of undefined
     env variable
   - Matches approach used in postLaunchSetup for consistency

2. HIGH: Orchestrator session leaks when metadata write fails
   - Added try-catch around tmux session launch and metadata write
   - Kills tmux session if metadata write or agent launch fails
   - Prevents orphaned sessions from consuming resources

3. MEDIUM: Dashboard process leaks when orchestrator setup fails
   - Wrapped orchestrator setup in try-catch block
   - Kills dashboard child process if orchestrator setup fails
   - Prevents orphaned Next.js server from blocking future starts

4. LOW: Inconsistent indentation (fixed as side effect of restructuring)
   - Refactored error handling simplified indentation structure

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Prateek 2026-02-15 15:00:24 +05:30
parent 05cfd2f5b5
commit 4f0fcd82b1
2 changed files with 94 additions and 88 deletions

View File

@ -207,105 +207,111 @@ export function registerStart(program: Command): void {
if (exists) {
console.log(chalk.yellow(`Orchestrator session "${sessionId}" is already running (skipping creation)`));
} else {
// Ensure CLAUDE.orchestrator.md exists
spinner.start("Generating orchestrator prompt");
ensureOrchestratorPrompt(
project.path,
config,
projectId,
project,
opts?.regenerate ?? false,
);
spinner.succeed("Orchestrator prompt ready");
// Ensure CLAUDE.local.md imports CLAUDE.orchestrator.md
spinner.start("Configuring CLAUDE.local.md");
try {
// Ensure CLAUDE.orchestrator.md exists
spinner.start("Generating orchestrator prompt");
ensureOrchestratorPrompt(
project.path,
config,
projectId,
project,
opts?.regenerate ?? false,
);
spinner.succeed("Orchestrator prompt ready");
// Ensure CLAUDE.local.md imports CLAUDE.orchestrator.md
spinner.start("Configuring CLAUDE.local.md");
ensureOrchestratorImport(project.path);
spinner.succeed("CLAUDE.local.md configured");
} catch (err) {
spinner.fail("Could not write CLAUDE.local.md");
throw new Error(
`Failed to update CLAUDE.local.md: ${err instanceof Error ? err.message : String(err)}`,
{ cause: err },
);
}
// Get agent instance (used for hooks and launch)
const agent = getAgent(config, projectId);
// Get agent instance (used for hooks and launch)
const agent = getAgent(config, projectId);
// Setup agent hooks for automatic metadata updates
spinner.start("Configuring agent hooks");
try {
// Setup agent hooks for automatic metadata updates
spinner.start("Configuring agent hooks");
if (agent.setupWorkspaceHooks) {
await agent.setupWorkspaceHooks(project.path, { dataDir: config.dataDir });
}
spinner.succeed("Agent hooks configured");
spinner.start("Creating orchestrator session");
// Get agent launch command
const launchCmd = agent.getLaunchCommand({
sessionId,
projectConfig: project,
permissions: project.agentConfig?.permissions ?? "default",
model: project.agentConfig?.model,
});
// Determine environment variables
const envVarName = `${project.sessionPrefix.toUpperCase().replace(/[^A-Z0-9_]/g, "_")}_SESSION`;
const environment: Record<string, string> = {
[envVarName]: sessionId,
AO_SESSION: sessionId,
AO_DATA_DIR: config.dataDir,
DIRENV_LOG_FORMAT: "",
};
// Merge agent-specific environment
const agentEnv = agent.getEnvironment({
sessionId,
projectConfig: project,
permissions: project.agentConfig?.permissions ?? "default",
model: project.agentConfig?.model,
});
Object.assign(environment, agentEnv);
// NOTE: AO_PROJECT_ID is intentionally not set for orchestrator (uses flat metadata path)
// Create tmux session
await newTmuxSession({
name: sessionId,
cwd: project.path,
environment,
});
try {
// Launch agent
await tmuxSendKeys(sessionId, launchCmd, true);
spinner.succeed("Orchestrator session created");
// Write metadata
const runtimeHandle = JSON.stringify({
id: sessionId,
runtimeName: "tmux",
data: {},
});
writeMetadata(config.dataDir, sessionId, {
worktree: project.path,
branch: project.defaultBranch,
status: "working",
project: projectId,
createdAt: new Date().toISOString(),
runtimeHandle,
});
} catch (err) {
// Cleanup tmux session if metadata write or agent launch fails
try {
await exec("tmux", ["kill-session", "-t", sessionId]);
} catch {
// Best effort cleanup - session may not exist
}
throw err;
}
} catch (err) {
spinner.fail("Could not setup agent hooks");
spinner.fail("Orchestrator setup failed");
// Cleanup dashboard if orchestrator setup fails
if (dashboardProcess) {
dashboardProcess.kill();
}
throw new Error(
`Failed to setup agent hooks: ${err instanceof Error ? err.message : String(err)}`,
`Failed to setup orchestrator: ${err instanceof Error ? err.message : String(err)}`,
{ cause: err },
);
}
spinner.start("Creating orchestrator session");
// Get agent launch command
const launchCmd = agent.getLaunchCommand({
sessionId,
projectConfig: project,
permissions: project.agentConfig?.permissions ?? "default",
model: project.agentConfig?.model,
});
// Determine environment variables
const envVarName = `${project.sessionPrefix.toUpperCase().replace(/[^A-Z0-9_]/g, "_")}_SESSION`;
const environment: Record<string, string> = {
[envVarName]: sessionId,
AO_SESSION: sessionId,
AO_DATA_DIR: config.dataDir,
DIRENV_LOG_FORMAT: "",
};
// Merge agent-specific environment
const agentEnv = agent.getEnvironment({
sessionId,
projectConfig: project,
permissions: project.agentConfig?.permissions ?? "default",
model: project.agentConfig?.model,
});
Object.assign(environment, agentEnv);
// NOTE: AO_PROJECT_ID is intentionally not set for orchestrator (uses flat metadata path)
// Create tmux session
await newTmuxSession({
name: sessionId,
cwd: project.path,
environment,
});
// Launch agent
await tmuxSendKeys(sessionId, launchCmd, true);
spinner.succeed("Orchestrator session created");
// Write metadata
const runtimeHandle = JSON.stringify({
id: sessionId,
runtimeName: "tmux",
data: {},
});
writeMetadata(config.dataDir, sessionId, {
worktree: project.path,
branch: project.defaultBranch,
status: "working",
project: projectId,
createdAt: new Date().toISOString(),
runtimeHandle,
});
}
}

View File

@ -710,9 +710,9 @@ function createClaudeCodeAgent(): Agent {
},
async setupWorkspaceHooks(workspacePath: string, _config: WorkspaceHooksConfig): Promise<void> {
// Use $CLAUDE_PROJECT_DIR variable for hook command
// (works in both main checkout and worktrees when .claude is symlinked)
await setupHookInWorkspace(workspacePath, '$CLAUDE_PROJECT_DIR/.claude/metadata-updater.sh');
// Use absolute path for hook command (specific to this workspace)
const hookScriptPath = join(workspacePath, ".claude", "metadata-updater.sh");
await setupHookInWorkspace(workspacePath, hookScriptPath);
},
async postLaunchSetup(session: Session): Promise<void> {