Skip to content

terraform_validate corrupts worktree index when tofu init clones modules (GIT_INDEX_FILE leakage) #992

@yuriipolishchuk

Description

@yuriipolishchuk

Describe the bug

When running git commit from a linked git worktree (git worktree add ...) on a stack that references modules, the commit fails at tree-build time with:

error: invalid object 100644 <oid> for '<some-upstream-module-file>'
error: Error building trees

even though every pre-commit hook reports Passed.

Root cause: terraform_validate invokes tofu init -backend=false, which shells out to git clone <module-source> for each module { source = ... } reference. The child git clone inherits GIT_INDEX_FILE / GIT_DIR / GIT_WORK_TREE / GIT_OBJECT_DIRECTORY from the parent git commit env (which git sets when invoking hooks). When the parent is in a worktree, GIT_INDEX_FILE points at .git/worktrees/<name>/index, and the child git clone writes the cloned module's blob OIDs INTO that index — with OIDs that exist only in .terraform/modules/<name>/.git/objects/. The subsequent git commit then fails to resolve those OIDs in the parent worktree's object DB.

This matches the maintainer's diagnosis on pre-commit/pre-commit#1849: pre-commit framework deliberately does NOT scrub GIT_* from hook subprocess env (no_git_env() is used only internally for pre-commit's own git calls). asottile's documented punt: "hook authors that do git writes need the same code as our no_git_env helper".

pre-commit-terraform's hook scripts currently do NOT scrub GIT_*. This bug fires consistently for any user running pre-commit from a git worktree on a stack with module references.

How can we reproduce it?

Reproduction script (~30s on a fresh setup)
# Setup
mkdir /tmp/repro && cd /tmp/repro
git init && git commit --allow-empty -m init
mkdir stack
cat > stack/main.tf <<'TF'
module "test" {
  source  = "terraform-aws-modules/iam/aws"
  version = "6.6.1"
}
TF
cat > .pre-commit-config.yaml <<'YAML'
repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.106.0
    hooks:
      - id: terraform_validate
        args:
          - --hook-config=--retry-once-with-cleanup=true
          - --tf-init-args=-backend=false
YAML
git add -A && git commit -m "baseline"

# Create the worktree (key step — the bug only fires from a worktree)
git worktree add ../repro-wt
cd ../repro-wt

# Install hooks + trigger
pre-commit install
echo "" >> stack/main.tf
git add stack/main.tf
git commit -m "test"

# Observe: hooks all pass, then:
#   error: invalid object 100644 <oid> for '<file from cloned module repo>'
#   error: Error building trees
git fsck --no-progress | head    # → invalid sha1 pointer in cache-tree of worktree index

The phantom blob OIDs in the worktree's index resolve to files inside the cloned module repo (.terraform/modules/test/.git/objects/), confirming git clone's blob writes went to the wrong index.

Environment information

  • OS: macOS 15.5 (Darwin arm64) — but the bug is platform-independent; we've seen it on Linux too
  • uname -a output:
Darwin <host> 25.5.0 Darwin Kernel Version 25.5.0: Mon Apr 27 20:41:12 PDT 2026; root:xnu-12377.121.6~2/RELEASE_ARM64_T6050 arm64
  • Tools availability and versions:
GNU bash, version 3.2.57(1)-release (arm64-apple-darwin25)
pre-commit 4.6.0
OpenTofu v1.12.0
Python 3.9.6
TFLint version 0.63.1
+ ruleset.terraform (0.15.0-bundled)
trivy Version: 0.70.0
  • .pre-commit-config.yaml:
file content
repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.106.0
    hooks:
      - id: terraform_validate
        args:
          - --hook-config=--retry-once-with-cleanup=true
          - --tf-init-args=-backend=false

Proposed fix

Add a common::scrub_git_env helper to hooks/_common.sh that mirrors pre-commit framework's own no_git_env helper (allowlist-based: keep GIT_SSH / GIT_ASKPASS / GIT_CONFIG_* etc. so SSH-source modules still authenticate; strip everything else GIT_*). Call it from main() of every hook script that shells to git, principally terraform_validate.sh, terraform_tflint.sh, and terraform_docs.sh.

Patch sketch (bash):

# hooks/_common.sh
function common::scrub_git_env {
  local var
  while IFS= read -r var; do
    case "$var" in
      GIT_CONFIG_KEY_*|GIT_CONFIG_VALUE_*) ;;
      GIT_EXEC_PATH|GIT_SSH|GIT_SSH_COMMAND|GIT_SSL_CAINFO) ;;
      GIT_SSL_NO_VERIFY|GIT_CONFIG_COUNT|GIT_HTTP_PROXY_AUTHMETHOD) ;;
      GIT_ALLOW_PROTOCOL|GIT_ASKPASS) ;;
      GIT_*) unset "$var" ;;
    esac
  done < <(env | sed -n 's/^\(GIT_[^=]*\)=.*/\1/p')
}
# hooks/terraform_validate.sh (and similarly tflint, docs)
function main {
  common::initialize "$SCRIPT_DIR"
  common::parse_cmdline "$@"
  common::export_provided_env_vars "${ENV_VARS[@]}"
  common::parse_and_export_env_vars
  common::scrub_git_env       # ← NEW
  common::per_dir_hook "$HOOK_ID" "${#ARGS[@]}" "${ARGS[@]}" "${FILES[@]}"
}

Net diff: ~15 lines _common.sh + 1 line per affected hook. No behavior change for non-worktree users (their GIT_* env is empty for these vars).

I'm happy to open a PR if the maintainer is receptive to this approach. Wanted to file the issue first for discussion since the alternative (Docker mode per pre-commit#3492) is the path the pre-commit author has suggested for this class of bug; happy to defer to the maintainer's preference between (a) in-script env scrub here, or (b) "use the Docker variant".

Related upstream issues

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions