shareAI-lab/learn-claude-code · error · RuntimeError
git {' '.join(args)} failed
Error message
git {' '.join(args)} failed What it means
Raised as RuntimeError by WorktreeManager._run_git() in agents/s12_worktree_task_isolation.py:262 when a git subprocess exits nonzero AND its combined stdout+stderr is empty, making the fallback message `git <args> failed` the error text. When git does print something (the usual case), its own message is surfaced instead — so seeing this exact message means git failed silently, which typically indicates a kill by signal (returncode < 0), an environment/PATH anomaly, or output eaten before capture.
Source
Thrown at agents/s12_worktree_task_isolation.py:262
timeout=10,
)
return r.returncode == 0
except Exception:
return False
def _run_git(self, args: list[str]) -> str:
if not self.git_available:
raise RuntimeError("Not in a git repository. worktree tools require git.")
r = subprocess.run(
["git", *args],
cwd=self.repo_root,
capture_output=True,
text=True,
timeout=120,
)
if r.returncode != 0:
msg = (r.stdout + r.stderr).strip()
raise RuntimeError(msg or f"git {' '.join(args)} failed")
return (r.stdout + r.stderr).strip() or "(no output)"
def _load_index(self) -> dict:
return json.loads(self.index_path.read_text())
def _save_index(self, data: dict):
self.index_path.write_text(json.dumps(data, indent=2))
def _find(self, name: str) -> dict | None:
idx = self._load_index()
for wt in idx.get("worktrees", []):
if wt.get("name") == name:
return wt
return None
def _validate_name(self, name: str):
if not re.fullmatch(r"[A-Za-z0-9._-]{1,40}", name or ""):
raise ValueError(View on GitHub (pinned to 985456f4ad)
Solutions
- Reproduce manually: run the same `git <args>` from the repo_root shell to get the real diagnostic
- If returncode < 0 (signal), check dmesg/OOM logs and free memory/disk; retry after freeing resources
- Verify the git binary: `which git && git --version` inside the harness environment; bypass wrappers/shims
- Upgrade the harness to include returncode and stderr in the fallback message so silent failures are diagnosable
Example fix
# before (s12)
if r.returncode != 0:
msg = (r.stdout + r.stderr).strip()
raise RuntimeError(msg or f"git {' '.join(args)} failed")
# after (richer diagnostics)
if r.returncode != 0:
msg = (r.stdout + r.stderr).strip()
raise RuntimeError(msg or f"git {' '.join(args)} failed (rc={r.returncode}, signal={-r.returncode if r.returncode < 0 else None})") Defensive patterns
Strategy: try-catch
Validate before calling
import subprocess
def git_will_succeed(args: list[str], repo_root: str) -> bool:
"""Dry-run probe: nonzero rc means git itself will report an error (with output);
rc == 0 means the real call will succeed."""
try:
r = subprocess.run(["git", "--no-pager", *args, "--dry-run" if args[0] in ("add", "commit") else "--"],
cwd=repo_root, capture_output=True, text=True, timeout=15)
except (OSError, subprocess.TimeoutExpired):
return False
return r.returncode == 0 Type guard
def is_git_failure_diagnosable(exc: RuntimeError) -> bool:
"""True when the RuntimeError carries git's own stderr (actionable);
False when it is the bare 'git ... failed' fallback (silent signal/env failure)."""
msg = str(exc)
return not msg.endswith("failed") Try / catch
import subprocess
try:
out = mgr._run_git(args)
except RuntimeError as e:
msg = str(e)
if msg.endswith("failed"):
# silent failure: rerun once to capture returncode/stderr for diagnosis
r = subprocess.run(["git", *args], cwd=repo_root, capture_output=True, text=True, timeout=120)
raise RuntimeError(f"git {' '.join(args)} failed rc={r.returncode}: {r.stderr.strip()}") from e
raise # git's own message — surface it Prevention
- Reproduce failing `git <args>` manually in repo_root to get the real diagnostic
- Check returncode sign: negative means a signal (OOM/kill) — inspect dmesg and free resources before retrying
- Verify the harness environment uses the real git binary (`which git`, no shims)
- Patch the harness to always include returncode and stderr in the error message
When it happens
Trigger: git worktree add/remove/checkout subprocess dying from SIGKILL (OOM killer), being terminated by the 120s timeout path's sibling behaviors, or a git binary mismatch (e.g. a shim that exits 1 with no output). Also reachable when args include values that make git exit nonzero without diagnostics.
Common situations: Resource-constrained containers where git is OOM-killed during a large worktree add. Custom git wrappers/aliases on PATH that fail quietly. Rare git versions with odd argument handling. Note: the subprocess call here has timeout=120 but the code shown does not catch TimeoutExpired, so that surfaces as a different error.
Related errors
- Not in a git repository. worktree tools require git.
- Task {task_id} not found
- Invalid status: {status}
- Invalid worktree name. Use 1-40 chars: letters, numbers, .,
- Worktree '{name}' already exists in index
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/2db2f28741dd73e7.
Report an issue: GitHub.