Skip to content

fix(hooks): clear repository git environment for tests - #1741

Open
bmcnaboe wants to merge 2 commits into
DeusData:mainfrom
bmcnaboe:codex/fix-precommit-git-environment
Open

fix(hooks): clear repository git environment for tests#1741
bmcnaboe wants to merge 2 commits into
DeusData:mainfrom
bmcnaboe:codex/fix-precommit-git-environment

Conversation

@bmcnaboe

Copy link
Copy Markdown

What does this PR do?

Clears Git's repository-local environment variables only for the pre-commit hook's test subprocess. This keeps staged-index context available to diff-scoped linting while preventing GIT_INDEX_FILE=.git/index from leaking into temporary linked-worktree fixtures.

Root cause

Git exports repository-local variables to hooks. The inherited relative index path works for a normal .git directory but fails when git worktree add checks out into a linked worktree where .git is a file.

Checklist

  • Every commit is signed off (git commit -s)
  • Tests pass locally (make -f Makefile.cbm test)
  • Lint passes (make -f Makefile.cbm lint-ci)
  • New behavior is covered by the poisoned-environment reproduction

Validation

  • GIT_INDEX_FILE=.git/index ... pipeline before: 249 passed, 1 failed
  • isolated environment after: 250 passed
  • real pre-commit hook: 7,555 passed, 8 skipped
  • scripts/test.sh: 7,555 passed, 0 failed, 8 skipped

Fixes #1740

Signed-off-by: Brian McNaboe <bmcnaboe@gmail.com>
@github-actions

Copy link
Copy Markdown

Thanks for opening this — it has been seen, and it is queued.

This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence.

Current review status: working through a backlog. 0.9.1-rc.1 is out, so the release freeze that held reviews is over — but it left a large queue of open pull requests behind it, and we are reading through them oldest-first. The background is in discussion #1144.

What that means for this PR, concretely:

  • It will not be closed for inactivity. No stale bot touches pull requests here.
  • It may still sit a while before a human reads it. That is on us, not on you.
  • Older PRs are read first, so a recent one is not being skipped — it is behind a queue.

Things that will genuinely speed it up whenever review does happen:

  • Keep it rebased on main — the tree is moving quickly right now, and a conflicting branch cannot be reviewed as the diff you intended.
  • Get CI green, or say which failures you believe are pre-existing.
  • Keep the change to one claim. Bundled features and refactors get split before they get merged, which costs you a round trip.
  • Every commit needs a sign-off (git commit -s) — CI enforces DCO.

If this fixes a bug, a reproduction we can run is worth more than a description of the symptom.

Thanks for contributing, and sorry in advance for the wait.

@bmcnaboe
bmcnaboe marked this pull request as ready for review August 24, 2026 19:54
@bmcnaboe
bmcnaboe requested a review from DeusData as a code owner August 24, 2026 19:54
pull Bot pushed a commit to Mu-L/codebase-memory-mcp that referenced this pull request Sep 1, 2026
The gate waited 90 x 30s = 45 min for CodeQL to finish on the PR head. That
is shorter than CodeQL actually takes on this repository, so the gate has
been failing runs that had not failed.

Measured on PR DeusData#1426, head 7b72652: the CodeQL SAST workflow completed with
conclusion=success at 17:46:05, having started at 15:41:44 -- 124 minutes.
The gate step ran 16:52:58 to 17:38:44 and reported "BLOCKED: CodeQL timeout"
7 minutes and 21 seconds before the scan it was waiting for succeeded.

Two open contributor pull requests are red from exactly this: DeusData#1426 and
DeusData#1769, both with CodeQL completed=success on their head and every other
check green.

Three further PRs (DeusData#1703, DeusData#1741, DeusData#1742) are also red on codeql-gate alone,
but from a different cause: the CodeQL run on their head is
completed=cancelled, so the gate saw a non-success conclusion and correctly
exited 1 without waiting. This change does not help those and is not
intended to; they need a fresh scan, most likely having been superseded by
concurrency cancel-in-progress in codeql.yml.

300 x 30s = 150 min covers the measured 124 min with margin. The job already
declares timeout-minutes: 240, so the wait still cannot outlive its own job.
No trigger, permission or gating change: codeql-gate blocks exactly what it
blocked before, and a genuine CodeQL failure still exits 1 immediately rather
than waiting out the budget. Only the absence of a verdict waits longer.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData DeusData added maintainer-notes Internal observations and improvement notes priority/normal Standard review queue; useful PR with ordinary maintainer urgency. labels Sep 1, 2026
@DeusData

DeusData commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Thank you for the poisoned-environment reproduction and for limiting the change to the hook test subprocess. The contribution queue is quite full, so review may take a little time. We have the PR routed and will come back with grounded feedback as soon as possible.

@DeusData

DeusData commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Approved. Ten lines, and three separate things in them are the right choice rather than the easy one.

The subshell makes the scoping structural. Wrapping the unsets in ( ... ) means the hook's own diff-scoped linting keeps its staged-index context and only the test subprocess runs clean. Doing it with a subshell rather than unsetting and restoring is what makes that true by construction instead of by discipline.

git rev-parse --local-env-vars is the authoritative list. On this machine that is fifteen variables — GIT_INDEX_FILE, GIT_DIR, GIT_WORK_TREE, GIT_COMMON_DIR, GIT_OBJECT_DIRECTORY and the rest. A hand-written list of the two or three that bite today would have been shorter, passed review, and quietly rotted as Git adds variables. Asking Git what it exports is the version that stays correct.

And the process substitution matters more than it looks. The obvious spelling —

git rev-parse --local-env-vars | while IFS= read -r v; do unset "$v"; done

— puts the loop in a further subshell, so every unset evaporates and the hook silently keeps the poisoned environment. < <(...) keeps the loop in the shell whose environment you are trying to clean. That is a trap people fall into repeatedly, and the version here is right. The shebang is #!/usr/bin/env bash, so the bashism is safe.

The incidental -j"$(...)" quoting fix is correct too.

The diagnosis is the good part though. "The inherited relative index path works for a normal .git directory but fails when git worktree add checks out into a linked worktree where .git is a file" is precisely it — a relative GIT_INDEX_FILE=.git/index resolves to something meaningless once .git stops being a directory. That is also why this only shows up in worktree-based fixtures, which is exactly how work on this repository is expected to be done, so the blast radius was larger than the issue count suggested.

Your CI is not your fault

All five reds are one cascade. security / codeql-gate ran from 16:08:24 to 16:54:00 — 45 minutes 36 seconds, which is the gate's old wait budget expiring rather than a finding. lint / lint and lint / lint-mem both record The operation was canceled with zero warnings of their own, and analyze and ci-ok fall out of the same run.

That budget was raised from 45 to 150 minutes in #1960, which merged yesterday. A re-run should come back clean.

I will update this branch and re-run before mergingmain moved three times yesterday (broken by a duplicate-symbol merge, repaired by #1993, then #1703 landed). Nothing needed from you.

Sorry it sat two weeks for a ten-line fix that unblocks a workflow the project itself recommends.

DeusData added a commit that referenced this pull request Sep 4, 2026
When a suite leader has already exited, taskkill /T cannot walk its tree,
so run-test-wave.py proves cleanup by asking Windows whether anything is
still parented to the dead PID. That probe was timed with --kill-grace,
which the parallel gate passes as 1s. A cold powershell.exe + CIM start
routinely costs more than that on a runner, so TimeoutExpired -- a fact
about interpreter start-up, not about the process tree -- was read as
"assume the worst" and failed an already-clean shard with exit 2.

Give the probe its own stable-state budget (15s) plus one retry, keeping
the fail-closed semantics: an exhausted or failed probe still counts as
"cannot prove absence". The refusal now names which of the two happened,
an unfinished probe or a counted set of live descendants, instead of
reporting both as a leaked tree.

The contract test pins this structurally -- no sleeps, no timing
thresholds: the probe must be bounded by the module constant rather than
by the kill-grace argument, must retry a timed-out probe, must never read
an unusable probe as absence, and must report the two failures
distinctly. Its Windows leader-exit case now waits on the scheduler's
refusal rather than on a deadline shorter than the new budget.

Refs #1741

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData

DeusData commented Sep 5, 2026

Copy link
Copy Markdown
Owner

The CLANG64 2/2 red on this PR was the harness, not your change: the parallel scheduler's Windows descendant probe was timed by --kill-grace (1 s), so a cold PowerShell start read as a leaked process tree. That is fixed on main in 202c8e4 (#2055). This branch only needs an update from main to pick it up, which I'll do when the runner queue has room — nothing for you to do here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer-notes Internal observations and improvement notes priority/normal Standard review queue; useful PR with ordinary maintainer urgency.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pre-commit hook leaks GIT_INDEX_FILE into linked-worktree tests

2 participants