feat(worker): cleanup planning worktree and branch on finalize/discard
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -140,9 +140,21 @@ public sealed class PlanningSessionManager
|
|||||||
|
|
||||||
public async Task<int> FinalizeAsync(string taskId, bool queueAgentTasks, CancellationToken ct)
|
public async Task<int> FinalizeAsync(string taskId, bool queueAgentTasks, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var (tasks, _, settings, ctx) = CreateRepos();
|
var (tasks, lists, settings, ctx) = CreateRepos();
|
||||||
await using var __ = ctx;
|
await using var __ = ctx;
|
||||||
return await tasks.FinalizePlanningAsync(taskId, queueAgentTasks, ct);
|
|
||||||
|
var count = await tasks.FinalizePlanningAsync(taskId, queueAgentTasks, ct);
|
||||||
|
|
||||||
|
// Best-effort cleanup — don't block finalization on git state.
|
||||||
|
await TryCleanupWorktreeAsync(taskId, lists, settings, ct);
|
||||||
|
|
||||||
|
var sessionDir = Path.Combine(_rootDirectory, taskId);
|
||||||
|
if (Directory.Exists(sessionDir))
|
||||||
|
{
|
||||||
|
try { Directory.Delete(sessionDir, recursive: true); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> GetPendingDraftCountAsync(string taskId, CancellationToken ct)
|
public async Task<int> GetPendingDraftCountAsync(string taskId, CancellationToken ct)
|
||||||
@@ -155,16 +167,19 @@ public sealed class PlanningSessionManager
|
|||||||
|
|
||||||
public async Task DiscardAsync(string taskId, CancellationToken ct)
|
public async Task DiscardAsync(string taskId, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var (tasks, _, settings, ctx) = CreateRepos();
|
var (tasks, lists, settings, ctx) = CreateRepos();
|
||||||
await using var __ = ctx;
|
await using var __ = ctx;
|
||||||
|
|
||||||
var ok = await tasks.DiscardPlanningAsync(taskId, ct);
|
var ok = await tasks.DiscardPlanningAsync(taskId, ct);
|
||||||
|
|
||||||
|
await TryCleanupWorktreeAsync(taskId, lists, settings, ct);
|
||||||
|
|
||||||
var sessionDir = Path.Combine(_rootDirectory, taskId);
|
var sessionDir = Path.Combine(_rootDirectory, taskId);
|
||||||
if (Directory.Exists(sessionDir))
|
if (Directory.Exists(sessionDir))
|
||||||
{
|
{
|
||||||
try { Directory.Delete(sessionDir, recursive: true); }
|
try { Directory.Delete(sessionDir, recursive: true); } catch { }
|
||||||
catch { /* best effort */ }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ok)
|
if (!ok)
|
||||||
throw new InvalidOperationException($"Task {taskId} was not in Planning state; nothing to discard.");
|
throw new InvalidOperationException($"Task {taskId} was not in Planning state; nothing to discard.");
|
||||||
}
|
}
|
||||||
@@ -192,9 +207,49 @@ public sealed class PlanningSessionManager
|
|||||||
|
|
||||||
var appSettings = await settings.GetAsync(ct);
|
var appSettings = await settings.GetAsync(ct);
|
||||||
var worktreePath = WorktreePathFor(taskId, appSettings.WorktreeStrategy, appSettings.CentralWorktreeRoot, listWorkingDir);
|
var worktreePath = WorktreePathFor(taskId, appSettings.WorktreeStrategy, appSettings.CentralWorktreeRoot, listWorkingDir);
|
||||||
var token = task.PlanningSessionToken ?? string.Empty;
|
if (!Directory.Exists(worktreePath))
|
||||||
|
throw new InvalidOperationException($"Planning worktree missing — cannot resume: {worktreePath}");
|
||||||
|
|
||||||
return new PlanningSessionResumeContext(taskId, listWorkingDir, task.PlanningSessionId, token, worktreePath);
|
var token = await ReadTokenFileAsync(TokenFilePathFor(sessionDir), ct);
|
||||||
|
|
||||||
|
return new PlanningSessionResumeContext(
|
||||||
|
ParentTaskId: taskId,
|
||||||
|
WorkingDir: worktreePath,
|
||||||
|
ClaudeSessionId: task.PlanningSessionId,
|
||||||
|
Token: token,
|
||||||
|
WorktreePath: worktreePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task TryCleanupWorktreeAsync(
|
||||||
|
string taskId,
|
||||||
|
ListRepository lists,
|
||||||
|
AppSettingsRepository settings,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (tasks, _, _, ctx2) = CreateRepos();
|
||||||
|
await using var __ = ctx2;
|
||||||
|
|
||||||
|
var task = await tasks.GetByIdAsync(taskId, ct);
|
||||||
|
if (task is null) return;
|
||||||
|
|
||||||
|
var list = await lists.GetByIdAsync(task.ListId, ct);
|
||||||
|
var listWorkingDir = list?.WorkingDir;
|
||||||
|
if (string.IsNullOrEmpty(listWorkingDir) || !Directory.Exists(listWorkingDir)) return;
|
||||||
|
|
||||||
|
var appSettings = await settings.GetAsync(ct);
|
||||||
|
var worktreePath = WorktreePathFor(taskId, appSettings.WorktreeStrategy, appSettings.CentralWorktreeRoot, listWorkingDir);
|
||||||
|
var branchName = BranchNameFor(taskId);
|
||||||
|
|
||||||
|
if (Directory.Exists(worktreePath))
|
||||||
|
{
|
||||||
|
try { await _git.WorktreeRemoveAsync(listWorkingDir, worktreePath, force: true, ct); }
|
||||||
|
catch { /* best effort */ }
|
||||||
|
}
|
||||||
|
try { await _git.BranchDeleteAsync(listWorkingDir, branchName, force: true, ct); } catch { }
|
||||||
|
}
|
||||||
|
catch { /* best effort — never block finalize/discard */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GenerateToken()
|
private static string GenerateToken()
|
||||||
|
|||||||
Reference in New Issue
Block a user