fix(agent/git): seed baseline commit for empty repos so results push back (#73)

Binding an empty repo (no commits) made `git clone` land an unborn HEAD with no
`origin/main`. `add_task_worktree` / `create_result_branch` then fell back to
base_ref=HEAD, and `git worktree add ... HEAD` failed with "invalid reference:
HEAD". The agent silently degraded to "executing on repo root without git push",
so the result branch never reached the remote.

Fix: after clone (and after refreshing an existing checkout), detect an empty
repo via `git rev-parse --verify HEAD` and plant one empty baseline commit on the
configured base branch. Worktree/branch creation and push then work normally and
the result branch is created on the (previously empty) remote.

Adds scripts/test-git-empty-repo.py covering the empty-repo path; existing
scripts/test-git-workflow.py still passes (no regression). Verified end-to-end
against a real empty GitHub repo: result branch pushed successfully.

Impact: Swarm agent git layer only (agent/git_operations.py). No change to
Manager<->Swarm contract, callbacks, billing, secret_ref, or audit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Fasthei
2026-06-16 22:35:27 +08:00
co-authored by Claude Opus 4.8
parent 5cf3b88c09
commit c38b5fd80d
2 changed files with 134 additions and 0 deletions
+43
View File
@@ -89,6 +89,7 @@ class GitOperations:
logger.info(f"Successfully cloned repository to {self.workspace_dir}")
await self._configure_git_user()
await self._ensure_baseline_commit()
return True
except Exception as e:
@@ -135,6 +136,48 @@ class GitOperations:
logger.error(f"Git clean failed: {clean.stderr}")
return False
await self._ensure_baseline_commit()
return True
async def _has_commits(self, cwd: str) -> bool:
"""True if the checkout has at least one commit (a valid, born HEAD)."""
result = await self._run_git_command(
["git", "rev-parse", "--verify", "--quiet", "HEAD"], cwd=cwd
)
return result.returncode == 0
async def _ensure_baseline_commit(self) -> bool:
"""Seed an initial commit when the clone is an empty repo (unborn HEAD, no commits).
An empty remote has no default branch and no commits, so the clone's HEAD is an
unborn ref. Downstream `git worktree add ... HEAD` / `checkout -B ... HEAD` then fail
with "invalid reference: HEAD", and the agent silently degrades to "without git push",
so results never reach the remote (agent_swarm#73). Planting one empty baseline commit
on the configured base branch gives those operations a valid base and lets the result
branch push back normally. No-op when the repo already has commits. Returns True if the
repo ends up with a born HEAD.
"""
cwd = await self.repo_root() or self.workspace_dir
if await self._has_commits(cwd):
return True
logger.warning(
"Cloned repository is empty (no commits); seeding baseline commit on '%s' "
"so task worktrees can be created and pushed (agent_swarm#73)",
self.base_branch,
)
# Land the first commit on the configured base branch even though HEAD is still unborn.
await self._run_git_command(["git", "checkout", "-B", self.base_branch], cwd=cwd)
commit = await self._run_git_command(
["git", "commit", "--allow-empty", "-m",
"chore: initialize empty repository baseline"],
cwd=cwd,
)
if commit.returncode != 0:
logger.error(f"Failed to seed baseline commit for empty repository: {commit.stderr}")
return False
logger.info(f"Seeded baseline commit on branch '{self.base_branch}' for empty repository")
return True
async def create_result_branch(self, task_id: Optional[str] = None) -> bool:
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env python3
"""Regression for agent_swarm#73: binding an EMPTY repo (no commits) must still
create a task worktree and push results back, instead of silently degrading to
"without git push". See agent/git_operations.py:_ensure_baseline_commit.
"""
import asyncio
import importlib.util
import os
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Optional
ROOT = Path(__file__).resolve().parents[1]
GIT_OPS_PATH = ROOT / "agent" / "git_operations.py"
spec = importlib.util.spec_from_file_location("git_operations", GIT_OPS_PATH)
git_operations = importlib.util.module_from_spec(spec)
assert spec and spec.loader
spec.loader.exec_module(git_operations)
GitOperations = git_operations.GitOperations
def run(command: list[str], cwd: Optional[Path] = None):
result = subprocess.run(
command,
cwd=cwd,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
if result.returncode != 0:
raise RuntimeError(
f"Command failed: {' '.join(command)}\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
)
return result
async def main():
with tempfile.TemporaryDirectory(prefix="swarm-git-empty-") as tmp:
tmpdir = Path(tmp)
remote = tmpdir / "remote.git"
workspace = tmpdir / "workspace"
worktree = tmpdir / "task-worktree"
# An EMPTY bare remote: no commits, no default branch — exactly the #73 scenario.
run(["git", "init", "--bare", "-b", "main", str(remote)])
os.environ["GIT_BASE_BRANCH"] = "main"
git_ops = GitOperations(str(workspace), "test-agent")
assert await git_ops.clone_workspace(str(remote)), "clone of empty repo failed"
assert await git_ops.is_git_workspace()
# Baseline must have been seeded so HEAD is now valid.
head = subprocess.run(
["git", "rev-parse", "--verify", "--quiet", "HEAD"],
cwd=workspace, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
)
assert head.returncode == 0 and head.stdout.strip(), "empty repo still has unborn HEAD"
# The previously-failing path: worktree add on an empty repo.
branch = await git_ops.add_task_worktree(str(worktree), "task-empty")
assert branch, "add_task_worktree returned None on empty repo (regression #73)"
# Do real work in the worktree and push it back.
task_git = GitOperations(str(worktree), "test-agent")
task_git.result_branch = branch
(worktree / "hello.py").write_text("print('hello from agent')\n", encoding="utf-8")
commit_sha = await task_git.commit_changes("Task task-empty: add hello")
assert commit_sha, "commit failed in empty-repo worktree"
pushed = await task_git.push_results()
assert pushed == branch, "push_results failed for empty-repo worktree"
refs = run(["git", "for-each-ref", "--format=%(refname:short)", "refs/heads"], cwd=remote)
assert branch in refs.stdout.splitlines(), "result branch missing on remote"
await git_ops.remove_task_worktree(str(worktree))
print("Empty-repo git workflow test passed")
print(f"branch={branch}")
print(f"commit={commit_sha}")
if __name__ == "__main__":
asyncio.run(main())