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

  1. Reproduce manually: run the same `git <args>` from the repo_root shell to get the real diagnostic
  2. If returncode < 0 (signal), check dmesg/OOM logs and free memory/disk; retry after freeing resources
  3. Verify the git binary: `which git && git --version` inside the harness environment; bypass wrappers/shims
  4. 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

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


AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14). Data as JSON: /api/errors/2db2f28741dd73e7. Report an issue: GitHub.