fix(worktree): auto-bootstrap commit-less repos before creating worktrees

Covers autonomous runs, interactive ConPTY sessions (GetInteractiveLaunchSpec),
planning sessions, and improvement/planning children — every path that needs a
base commit now self-heals on a fresh 'git init' repo instead of surfacing
"ambiguous argument 'HEAD'" as a HubException.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit ec16a85c495ceceaa22f922e077a27dcda54f035)
This commit is contained in:
CubeGameLP
2026-08-28 20:33:45 +02:00
parent de44196b9a
commit e0748ba351
3 changed files with 35 additions and 0 deletions
@@ -98,6 +98,9 @@ public sealed class PlanningSessionManager
if (!await _git.IsGitRepoAsync(listWorkingDir, ct))
throw new InvalidOperationException($"Working directory is not a git repository: {listWorkingDir}");
// A fresh `git init` repo has no HEAD commit to base the planning worktree on.
await _git.EnsureHeadCommitAsync(listWorkingDir, ct);
var appSettings = await settings.GetAsync(ct);
var worktreePath = WorktreePathFor(taskId, appSettings.WorktreeStrategy, appSettings.CentralWorktreeRoot, listWorkingDir);
var branchName = BranchNameFor(taskId);
@@ -46,6 +46,10 @@ public sealed class WorktreeManager
if (!await _git.IsGitRepoAsync(workingDir, ct))
throw new InvalidOperationException($"working_dir is not a git repository: {workingDir}");
// A fresh `git init` repo has no HEAD commit to base a worktree on — bootstrap one.
if (await _git.EnsureHeadCommitAsync(workingDir, ct))
_logger.LogInformation("Repository at {Dir} had no commits; created an empty bootstrap commit", workingDir);
var baseCommit = await ResolveBaseCommitAsync(task, workingDir, ct);
// Use the full task id (dashes stripped) in the branch name so
// two GUIDs sharing an 8-char prefix cannot collide on the same branch.