vercel/turborepo · warning
failed to run git diff for dirty hash
Error message
failed to run git diff for dirty hash
What it means
Continuation of the dirty-hash computation: turbo streams `git diff HEAD --no-ext-diff --no-color` into the hasher and falls back to `git diff --cached` when that is unavailable (the fallback exists to capture staged content without needing HEAD). If both attempts return None, this warning is emitted and diff_has_content becomes false, so only `git status` filenames feed the dirty hash — or the whole dirty hash is skipped when status was empty too.
Source
Thrown at crates/turborepo-scm/src/git.rs:374
fn finish_dirty_hash(&self, mut hasher: sha2::Sha256, has_status: bool) -> Option<String> {
use sha2::Digest;
// Try `git diff HEAD` first. In a freshly initialized repo with no
// commits, HEAD doesn't exist and git exits with code 128. Fall back
// to `git diff --cached` which diffs the index against an empty tree,
// correctly capturing staged file content without needing HEAD.
let diff_has_content = match self.stream_diff_into_hasher(
&["diff", "HEAD", "--no-ext-diff", "--no-color"],
&mut hasher,
) {
Some(has_content) => has_content,
None => match self.stream_diff_into_hasher(
&["diff", "--cached", "--no-ext-diff", "--no-color"],
&mut hasher,
) {
Some(has_content) => has_content,
None => {
turborepo_log::warn(
turborepo_log::Source::turbo(turborepo_log::Subsystem::Scm),
"failed to run git diff for dirty hash",
)
.emit();
false
}
},
};
if !has_status && !diff_has_content {
return None;
}
Some(hex::encode(hasher.finalize()))
}
/// Spawn a git diff subprocess, streaming its stdout into `hasher`.
/// Returns whether the diff had content, or `None` if the command failed.View on GitHub (pinned to f9245100cf)
Solutions
- Verify both `git diff HEAD` and `git diff --cached` run cleanly in the repo
- Fix git environment issues (PATH, safe.directory, reinstall git)
- If .git is corrupted, re-clone the repository
- Note the consequence: without diff content, cache keys rely only on git status filenames
Example fix
# before: fresh repo, no commits, both diffs fail git init && turbo run build # after git add -A && git commit -m 'initial commit' turbo run build
Defensive patterns
Strategy: validation
Validate before calling
# Both diff forms must be runnable for a complete dirty hash git -C "$REPO_ROOT" diff HEAD >/dev/null 2>&1 || echo 'git diff HEAD failed' git -C "$REPO_ROOT" diff --cached >/dev/null 2>&1 || echo 'git diff --cached failed'
Prevention
- Commit at least once (`git commit --allow-empty -m init`) before running turbo in fresh repos
- Verify git subprocess execution works inside sandboxes that restrict spawning
- Keep .git healthy — avoid interrupting git mid-operation
- If both diffs fail, expect coarser cache keys: only git status filenames feed the dirty hash
When it happens
Trigger: Both `git diff HEAD` and `git diff --cached` fail in the repo: broken git installation or corrupted object database, an environment where git child processes cannot run (PATH/sandbox restrictions), or a repository state in which both diff invocations error.
Common situations: Environments that block git subprocess spawning; corrupted .git after disk issues; exotic git versions or configs breaking diff execution; combined with error 104, a totally non-functional git setup.
Related errors
- failed to get git status for dirty hash: {e}
- --affected could not determine changed files. All tasks will
- Directory path contains potentially unsafe characters: ${dir
- Invalid ${description}: path must be an absolute, non-empty
- `git ${args[0]}` failed: ${formatError(error)}
AI-assisted analysis of vercel/turborepo@f9245100cf (2026-08-17).
Data as JSON: /api/errors/482b5af3f0e85f27.
Report an issue: GitHub.