ErrLookup › Background articles › "git command failed": what it means when a tool shells out to git and git exits non-zero
"git command failed": what it means when a tool shells out to git and git exits non-zero
"git command failed" errors appear when a library, CLI, or build tool runs a git subprocess (status, diff, rev-parse, fetch, worktree, merge-base, and so on) and git exits with a non-zero status. The wrapper message itself is rarely the diagnosis — the real cause is git's stderr or the repository state: not a git repo, unborn HEAD, a missing ref, an index.lock file, dubious ownership, authentication failures, or a corrupt object database.
Distilled from 101 documented records across 21 repositories.
Background
This family sits at the boundary between application code and the git binary. Instead of linking a git library, many tools — turbo, deno, mise, dagger, zed, windmill, grok-build, oh-my-pi, and others — spawn `git` as a child process and treat any non-zero exit as an error. The application's message is a wrapper: what actually failed is the git subcommand inside it. That's why these errors look generic (`git {} failed`, `git command failed`, `git failed with status {status}`) while naming wildly different root causes.
How much detail the wrapper carries is library-specific. Some records embed git's own stderr directly — deno's `git {} failed: {}`, mise's `git -C {} {} failed: {}`, windmill's `git ... failed (exit N): <stderr>`, and the `%w: %s` Go wrappers in dagger and open-code-review all append git's diagnostic, so the message is usually self-explanatory. Others deliberately omit it: nikivdev/code captures stdout instead of inheriting it and bails with a bare `git <args> failed`, mise's streaming path prints git's output to your terminal before bailing with only the exit status, and grok-build falls back to a generic "git command failed" when git exits non-zero with empty stderr. A few wrap richer structures: oh-my-pi raises a GitCommandError carrying the command, return code, and redacted stdout/stderr; zed includes the process status plus collected stderr.
From the caller's side, the error means the tool asked git to do something and git refused — not that the tool itself is broken. The same wrapper surfaces for many distinct repository states: running in a directory that isn't a repo, an unborn branch with no commits, a ref that doesn't exist (or a main-vs-master rename), a stale `.git/index.lock` left by a crashed git process, `safe.directory` dubious-ownership refusals, missing user identity for commit/config operations, authentication or network failures on fetch/push, and genuinely corrupt object databases or indexes. Environment problems also land here: git missing from the PATH (especially for GUI apps and services that inherit a different PATH than your shell), sandboxed environments that forbid spawning subprocesses, timeouts killing long git operations, and read-only or full disks.
The family also varies in consequence. Some tools degrade gracefully — turbo's dirty-hash path warns and falls back to coarser cache keys built only from `git status` filenames, and zed treats unborn-HEAD log failures as empty history. Others hard-fail by design: beads refuses to continue after a worktree create unless `git status` proves the checkout clean, and oh-my-pi deliberately raises on a failed `git worktree prune` (including the 124 timeout code) so the cleanup event retries instead of recording success while stale metadata blocks the next worktree add. Edge cases worth knowing: git uses exit code 1 as a legitimate "no" answer for `merge-base --is-ancestor`, so zed treats only codes above 1 as infrastructure failure; and jj (Jujutsu) has a parallel failure path in grok-build with the same empty-stderr fallback shape.
Common causes
- Not a git repository (or unborn HEAD). Running the tool outside a repo, against a bad repo_root, or in a fresh repo with no commits yet makes commands like `git rev-parse`, `git diff HEAD`, or `git log` exit 128. This is the most frequently cited trigger across the records, and it also covers refs like HEAD that don't resolve before the first commit.
- Missing or invalid ref/branch/revision. The referenced branch, tag, commit, or --changed base doesn't exist locally — often because it was deleted upstream, the clone is shallow, or the name is stale (main vs master renames). Git exits non-zero with messages like 'bad object' or 'unknown revision'.
- Stale index.lock or concurrent git operations. A crashed or concurrent git process leaves `.git/index.lock` behind, so subsequent commands refuse to run — sometimes with empty stderr, which produces the generic fallback variants of this error. Half-finished clones and worktree adds cause similar stuck states.
- Dubious ownership (safe.directory). Containers and CI sharing a host checkout, or running as root/sudo against a user-owned repo, make git refuse the repository with a 'dubious ownership' error. The tool reports a generic git failure while the fix is adding the repo to safe.directory or fixing ownership.
- Authentication, network, and remote failures. Fetch/pull/push/clone operations fail on missing credentials (no credential helper or SSH key), rejected pushes (non-fast-forward, auth), hung remotes, or proxies. In streaming wrappers like mise's, git's real message has already scrolled past and only the exit status remains.
- git missing from PATH or unspawnable. The subprocess can't start at all — ENOENT because git isn't installed or the service/GUI process inherited a different PATH than your shell, or sandbox restrictions forbid spawning children. Some wrappers distinguish this (a distinct spawn-error message); others fold it into the same failure.
- Corrupt repository, index, or disk problems. Interrupted operations, full disks, or damaged .git metadata produce failing diffs, rev-parse, merge-base, and status calls. `git fsck` is the standard diagnostic, and re-cloning is the usual remedy when corruption is confirmed.
- Missing git identity or invalid inputs. Commands that create commits or set config fail when user.name/user.email aren't configured (common in bare CI containers), and operations like `git branch -m` fail on invalid target names or renaming over an existing branch.
What usually fixes it
- [object Object]
- [object Object]
- [object Object]
- [object Object]
- [object Object]
Go deeper
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Documented occurrences
- git {} failed (nikivdev/code)
- failed to run git diff for dirty hash (vercel/turborepo)
- `git {}` failed: {} (denoland/deno)
- git failed with status {status} (jdx/mise)
- git command failed (xai-org/grok-build)
- failed to inspect created worktree cleanliness: %w %s (gastownhall/beads)
- Git operation failed: git -C ${cwd} ${args.join(' ')} (thedotmack/claude-mem)
- GitCommandError(["git", *args], proc.returncode, proc.stdout, proc.stderr) (can1357/oh-my-pi)
- %w: %s (alibaba/open-code-review)
- failed to expire reflog for remote %s: %w (dagger/dagger)
- git {shown} failed in {}: {} (xai-org/grok-build)
- failed to get git status for dirty hash: {e} (vercel/turborepo)
- git ${args.join(" ")} failed (exit ${status}): ${r.stderr ?? ""} (windmill-labs/windmill)
- git %s: %w (dagger/dagger)
- %w: %s (gastownhall/beads)
- git %v: %w: %s (dagger/dagger)
- failed to rev-parse: %w (dagger/dagger)
- GitCommandError: git branch -m failed (nonzero exit) (can1357/oh-my-pi)
- GitCommandError: git config user.email/user.name failed (nonzero exit) (can1357/oh-my-pi)
- ${command} ${args.join(" ")} failed: ${detail} (EveryInc/compound-engineering-plugin)
…and 81 more across the corpus — use search.
Honest provenance: generated on 2026-09-05 from AI-assisted analysis of the linked records. See how records are made.