fix(KB-647): add conflict recovery to worktree creation fallback

- Add conflict recovery to createFromExistingBranch fallback in executor\n- Add tests for worktree conflict recovery scenarios\n- Add changeset documenting the worktree recovery fix\n- Clean up removed central-core modules and AGENTS.md content\n- Update CLI command descriptions and extension tools
This commit is contained in:
gsxdsm
2026-03-31 20:01:35 -07:00
parent 80ab88db3d
commit 9fd7b667d5
3 changed files with 176 additions and 23 deletions

View File

@@ -1363,31 +1363,16 @@ If issues are found that need attention, describe them clearly.`;
// Handle "already used by worktree" conflict
if (conflictInfo.type === "already-used" && conflictInfo.path) {
const shouldGenerateNewName = await this.shouldGenerateNewWorktreeName(
conflictInfo.path,
taskId,
);
if (shouldGenerateNewName) {
// Conflicting worktree belongs to an active task - generate new name
const newPath = join(this.rootDir, ".worktrees", generateWorktreeName(this.rootDir));
await this.store.logEntry(
taskId,
`Conflicting worktree in use by active task, trying new path`,
newPath,
);
return this.tryCreateWorktree(branch, newPath, taskId, startPoint, attemptNumber);
}
// Safe to clean up - conflicting worktree is not in use
const cleanupSuccess = await this.cleanupConflictingWorktree(
const result = await this.handleWorktreeConflict(
conflictInfo.path,
branch,
path,
taskId,
startPoint,
attemptNumber,
);
if (cleanupSuccess) {
await this.store.logEntry(taskId, `Cleaned up conflicting worktree, retrying`, path);
return this.tryCreateWorktree(branch, path, taskId, startPoint, attemptNumber);
if (result) {
return result;
}
throw new Error(
`Worktree conflict at ${conflictInfo.path}: automatic cleanup failed`,
@@ -1419,12 +1404,71 @@ If issues are found that need attention, describe them clearly.`;
createFromExistingBranch();
executorLog.log(`Worktree created from existing branch: ${path}`);
return path;
} catch (e: any) {
throw new Error(`Failed to create worktree: ${e.message}`);
} catch (fallbackError: any) {
// Check if the fallback also hit an "already used" conflict
const fallbackConflictInfo = this.extractWorktreeConflictInfo(fallbackError);
if (fallbackConflictInfo.type === "already-used" && fallbackConflictInfo.path) {
const result = await this.handleWorktreeConflict(
fallbackConflictInfo.path,
branch,
path,
taskId,
startPoint,
attemptNumber,
);
if (result) {
return result;
}
throw new Error(
`Worktree conflict at ${fallbackConflictInfo.path}: automatic cleanup failed`,
);
}
throw new Error(`Failed to create worktree: ${fallbackError.message}`);
}
}
}
/**
* Handle "already used by worktree" conflict.
* Either generates a new worktree name (if conflicting worktree is in use by active task)
* or cleans up the conflicting worktree and retries.
*
* @returns The worktree path if recovery succeeded, null if recovery failed
*/
private async handleWorktreeConflict(
conflictPath: string,
branch: string,
path: string,
taskId: string,
startPoint?: string,
attemptNumber?: number,
): Promise<string | null> {
const shouldGenerateNewName = await this.shouldGenerateNewWorktreeName(
conflictPath,
taskId,
);
if (shouldGenerateNewName) {
// Conflicting worktree belongs to an active task - generate new name
const newPath = join(this.rootDir, ".worktrees", generateWorktreeName(this.rootDir));
await this.store.logEntry(
taskId,
`Conflicting worktree in use by active task, trying new path`,
newPath,
);
return this.tryCreateWorktree(branch, newPath, taskId, startPoint, attemptNumber);
}
// Safe to clean up - conflicting worktree is not in use
const cleanupSuccess = await this.cleanupConflictingWorktree(conflictPath, branch, taskId);
if (cleanupSuccess) {
await this.store.logEntry(taskId, `Cleaned up conflicting worktree, retrying`, path);
return this.tryCreateWorktree(branch, path, taskId, startPoint, attemptNumber);
}
return null;
}
/**
* Check if a path is registered as a git worktree.
*/