# Empty-repo hardening (unborn HEAD) — design **Date:** 2026-07-30 **Status:** approved (option "Auto-Bootstrap" chosen by Mika) ## Problem `GetInteractiveLaunchSpec` (embedded ConPTY session) fails with a raw git error when the task's list points at a git repository that has **no commits yet** (fresh `git init`, unborn HEAD): ``` HubException: git rev-parse HEAD failed (exit 128): fatal: ambiguous argument 'HEAD': unknown revision ... ``` Root cause chain: `InteractiveLaunchSpecService.BuildForTaskAsync` → `WorktreeManager.CreateAsync` → `IsGitRepoAsync` (passes — `rev-parse --git-dir` works on an empty repo) → `ResolveBaseCommitAsync` → `RevParseHeadAsync` (fatals — HEAD is unborn). The same gap exists in `PlanningSessionManager.StartAsync` and hits autonomous runs (`TaskRunner` → `CreateAsync` → task `Failed` with the same cryptic message). This is a "works on my laptop" class of bug: on a fresh PC the first thing a user tries is a freshly init-ed test repo, which breaks instantly. A second member of the same class: a PC without `git config user.name/email` breaks every auto-commit ("Please tell me who you are"). ## Decision **Auto-bootstrap:** when a worktree (task run / ConPTY / planning) needs a base commit and the repo has zero commits, ClaudeDo creates an empty initial commit automatically and proceeds. Nothing can be lost — the repo is empty. Genuinely broken states (HEAD not a symbolic ref and not resolvable) still produce a clear, actionable error instead of raw git stderr. ## Design ### 1. `GitService.HasHeadCommitAsync(dir)` `git rev-parse --verify --quiet HEAD` → exit 0 means HEAD resolves to a commit. ### 2. `GitService.EnsureHeadCommitAsync(dir)` → `bool` (true = bootstrapped) - HEAD resolves → return `false` (no-op). - `git symbolic-ref -q HEAD` fails → repo is corrupt (detached HEAD onto a missing commit, mangled `.git/HEAD`) → throw `InvalidOperationException` with a clear message naming the directory. Auto-fixing a corrupt repo is out of scope by design. - Otherwise HEAD is an unborn branch → bootstrap **via plumbing only** (never touches the user's index or working tree — staged/untracked files stay exactly as they were): 1. `git hash-object -w -t tree --stdin` with empty stdin → empty tree object. 2. `git -c user.name=ClaudeDo -c user.email=claudedo@local commit-tree -m "chore: initialize repository (ClaudeDo)"` — inline identity so this works on a PC with no git identity configured. 3. `git update-ref `. - Serialized behind a process-wide gate with a re-check after acquiring, so two parallel task starts on the same empty repo bootstrap exactly once. ### 3. Defense-in-depth: friendly `RevParseHeadAsync` error When `rev-parse HEAD` fails with the unborn-HEAD signature ("ambiguous argument 'HEAD'"), throw "The repository at {dir} has no commits yet" instead of raw stderr — covers any future call site that forgets the preflight. ### 4. Call sites - `WorktreeManager.CreateAsync` — after the `IsGitRepoAsync` guard, call `EnsureHeadCommitAsync`; log when a bootstrap happened. Covers autonomous runs, interactive ConPTY sessions, improvement/planning children. - `PlanningSessionManager.StartAsync` — same, before its own `RevParseHeadAsync`. ### 5. Git identity fallback for auto-commits `GitService.CommitAsync`: if the commit fails with the missing/empty-identity signature, retry once with inline `-c user.name="ClaudeDo" -c user.email="claudedo@local"`. Happy path costs nothing (no preflight call); a fresh PC without git identity no longer fails every task auto-commit. ## Out of scope - UI validation when configuring a list's WorkingDir (nice-to-have; the worktree paths now either self-heal or produce clear errors). - Repairing corrupt repos. ## Test plan (real git, Worker.Tests) - `HasHeadCommitAsync`: false on fresh `git init`, true after a commit. - `EnsureHeadCommitAsync` on an empty repo: returns true, `rev-parse HEAD` then works, tree of the bootstrap commit is empty, second call returns false. - Staged-file preservation: file staged in empty repo → after bootstrap it is still staged and NOT part of the bootstrap commit. - Corrupt HEAD (point `.git/HEAD` at a bogus ref? no — detach onto a garbage SHA by writing `.git/HEAD` directly): clear error, no bootstrap. - `CommitAsync` identity fallback: local `user.name`/`user.email` set to empty strings (forces the identity failure deterministically regardless of global config) → commit succeeds via fallback, author is ClaudeDo. - `WorktreeManager.CreateAsync` on an empty repo: succeeds; worktree exists; base commit = bootstrap commit. - `PlanningSessionManager.StartAsync` on an empty repo: succeeds (if existing test infra makes this cheap; otherwise covered by the shared code path).