fix(worker): clean up orphaned worktree when the DB row insert fails
If WorktreeAddAsync succeeds but the worktrees-row insert throws, the worktree was left on disk and branch undeleted with nothing tracking it. Wrap the insert in try/catch and best-effort remove the worktree+branch (non-cancellable) before rethrowing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -92,21 +92,37 @@ public sealed class WorktreeManager
|
||||
}
|
||||
|
||||
// Insert worktrees row AFTER git succeeds — if git throws, no row is created.
|
||||
using var context = _dbFactory.CreateDbContext();
|
||||
var wtRepo = new WorktreeRepository(context);
|
||||
// Drop any stale row from a prior run (force-remove may have left the DB side behind).
|
||||
await wtRepo.DeleteAsync(task.Id, ct);
|
||||
await wtRepo.AddAsync(new WorktreeEntity
|
||||
// If the insert itself fails, the worktree is already on disk with nothing
|
||||
// tracking it: remove it (best-effort, non-cancellable) before rethrowing.
|
||||
try
|
||||
{
|
||||
TaskId = task.Id,
|
||||
Path = worktreePath,
|
||||
BranchName = branchName,
|
||||
BaseCommit = baseCommit,
|
||||
HeadCommit = null,
|
||||
DiffStat = null,
|
||||
State = WorktreeState.Active,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
}, ct);
|
||||
using var context = _dbFactory.CreateDbContext();
|
||||
var wtRepo = new WorktreeRepository(context);
|
||||
// Drop any stale row from a prior run (force-remove may have left the DB side behind).
|
||||
await wtRepo.DeleteAsync(task.Id, ct);
|
||||
await wtRepo.AddAsync(new WorktreeEntity
|
||||
{
|
||||
TaskId = task.Id,
|
||||
Path = worktreePath,
|
||||
BranchName = branchName,
|
||||
BaseCommit = baseCommit,
|
||||
HeadCommit = null,
|
||||
DiffStat = null,
|
||||
State = WorktreeState.Active,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
}, ct);
|
||||
}
|
||||
catch (Exception dbEx)
|
||||
{
|
||||
_logger.LogError(dbEx,
|
||||
"Failed to record worktree row for task {TaskId}; removing orphaned worktree at {Path}",
|
||||
task.Id, worktreePath);
|
||||
try { await _git.WorktreeRemoveAsync(workingDir, worktreePath, force: true, CancellationToken.None); }
|
||||
catch (Exception rmEx) { _logger.LogWarning(rmEx, "Failed to remove orphaned worktree at {Path}", worktreePath); }
|
||||
try { await _git.BranchDeleteAsync(workingDir, branchName, force: true, CancellationToken.None); }
|
||||
catch (Exception delEx) { _logger.LogWarning(delEx, "Failed to delete orphaned branch {Branch}", branchName); }
|
||||
throw;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Created worktree for task {TaskId} at {Path} (branch {Branch}, base {Base})",
|
||||
task.Id, worktreePath, branchName, baseCommit);
|
||||
|
||||
Reference in New Issue
Block a user