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

  1. Verify both `git diff HEAD` and `git diff --cached` run cleanly in the repo
  2. Fix git environment issues (PATH, safe.directory, reinstall git)
  3. If .git is corrupted, re-clone the repository
  4. 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

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


AI-assisted analysis of vercel/turborepo@f9245100cf (2026-08-17). Data as JSON: /api/errors/482b5af3f0e85f27. Report an issue: GitHub.