{"record":{"id":"2db2f28741dd73e7","repo":"shareAI-lab/learn-claude-code","slug":"git-join-args-failed","errorCode":null,"errorMessage":"git {' '.join(args)} failed","messagePattern":"git (.+?) failed","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"agents/s12_worktree_task_isolation.py","lineNumber":262,"sourceCode":"                timeout=10,\n            )\n            return r.returncode == 0\n        except Exception:\n            return False\n\n    def _run_git(self, args: list[str]) -> str:\n        if not self.git_available:\n            raise RuntimeError(\"Not in a git repository. worktree tools require git.\")\n        r = subprocess.run(\n            [\"git\", *args],\n            cwd=self.repo_root,\n            capture_output=True,\n            text=True,\n            timeout=120,\n        )\n        if r.returncode != 0:\n            msg = (r.stdout + r.stderr).strip()\n            raise RuntimeError(msg or f\"git {' '.join(args)} failed\")\n        return (r.stdout + r.stderr).strip() or \"(no output)\"\n\n    def _load_index(self) -> dict:\n        return json.loads(self.index_path.read_text())\n\n    def _save_index(self, data: dict):\n        self.index_path.write_text(json.dumps(data, indent=2))\n\n    def _find(self, name: str) -> dict | None:\n        idx = self._load_index()\n        for wt in idx.get(\"worktrees\", []):\n            if wt.get(\"name\") == name:\n                return wt\n        return None\n\n    def _validate_name(self, name: str):\n        if not re.fullmatch(r\"[A-Za-z0-9._-]{1,40}\", name or \"\"):\n            raise ValueError(","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/agents/s12_worktree_task_isolation.py#L244-L280","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before (s12)\n        if r.returncode != 0:\n            msg = (r.stdout + r.stderr).strip()\n            raise RuntimeError(msg or f\"git {' '.join(args)} failed\")\n\n# after (richer diagnostics)\n        if r.returncode != 0:\n            msg = (r.stdout + r.stderr).strip()\n            raise RuntimeError(msg or f\"git {' '.join(args)} failed (rc={r.returncode}, signal={-r.returncode if r.returncode < 0 else None})\")","handlingStrategy":"try-catch","validationCode":"import subprocess\n\ndef git_will_succeed(args: list[str], repo_root: str) -> bool:\n    \"\"\"Dry-run probe: nonzero rc means git itself will report an error (with output);\n    rc == 0 means the real call will succeed.\"\"\"\n    try:\n        r = subprocess.run([\"git\", \"--no-pager\", *args, \"--dry-run\" if args[0] in (\"add\", \"commit\") else \"--\"],\n                           cwd=repo_root, capture_output=True, text=True, timeout=15)\n    except (OSError, subprocess.TimeoutExpired):\n        return False\n    return r.returncode == 0","typeGuard":"def is_git_failure_diagnosable(exc: RuntimeError) -> bool:\n    \"\"\"True when the RuntimeError carries git's own stderr (actionable);\n    False when it is the bare 'git ... failed' fallback (silent signal/env failure).\"\"\"\n    msg = str(exc)\n    return not msg.endswith(\"failed\")","tryCatchPattern":"import subprocess\n\ntry:\n    out = mgr._run_git(args)\nexcept RuntimeError as e:\n    msg = str(e)\n    if msg.endswith(\"failed\"):\n        # silent failure: rerun once to capture returncode/stderr for diagnosis\n        r = subprocess.run([\"git\", *args], cwd=repo_root, capture_output=True, text=True, timeout=120)\n        raise RuntimeError(f\"git {' '.join(args)} failed rc={r.returncode}: {r.stderr.strip()}\") from e\n    raise  # git's own message — surface it","preventionTips":["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"],"tags":["git","worktree","subprocess","diagnostics","python"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}