Skip to content

Keep test binaries out of the repository whose git hook runs them - #144

Open
joelachance wants to merge 1 commit into
mainfrom
claude/amazing-payne-399ef5
Open

joelachance wants to merge 1 commit into
mainfrom
claude/amazing-payne-399ef5

Conversation

@joelachance

Copy link
Copy Markdown
Contributor

Notes for the reviewer:

  • Root cause of the Test User <test@example.com> identity in ~/git/gx/.git/config: on 2026-08-15 the CLI suite ran as the prepare-commit-msg hook in a linked worktree. That was the cli.test binary, pinned into the hooks by a test, fixed in 7d3a040. Git exports an absolute GIT_DIR to every hook in a linked worktree, so the suite's temp-dir git config / git init --bare calls wrote the real common config, setting core.bare = true and the identity. The bare flag was reverted by hand that night; the identity stayed.
  • To reproduce on main, run go test ./internal/{cli,codereview,vcs,hooks}/ from a pre-commit hook in a scratch repo's linked worktree. 167 test commits land in the scratch repo, its main branch is rewritten, gx's hooks are installed into it, and its config gains bare = true, user.name, a fake origin and gx.enabled = false. With this branch, the full suite run from that hook passes all 37 packages and leaves the scratch repo untouched.
  • The fix is per process, in TestMain, not per helper, because production code under test spawns git too. cmd/gx's tests never run git themselves, yet they installed the hooks through cli.NewRoot's auto-init.
  • Most of the diff is 23 three-line TestMains. The rest is internal/gxtest/gitenv.go and gitenv_test.go.
  • I found no production hook path that runs tests. gx review's static tools already drop GIT_*, and that is now pinned by a test. background.StartDetachedGx still passes the hook's GIT_DIR to the detached workers, but they don't run git today.

🤖 Generated with Claude Code

Git exports GIT_DIR to every hook it runs from a linked worktree, and every
checkout under .claude/worktrees is one. A child git honours GIT_DIR over its
working directory, so a test that runs `git -C <t.TempDir()> config user.name
"Test User"` inside a hook writes the enclosing repository's shared
.git/config instead of its temp repo. That is how gx's own clone came to
carry Test User <test@example.com>: on 2026-08-15 the CLI suite ran as the
prepare-commit-msg hook in a worktree (a test had pinned the hook to the
cli.test binary, since fixed) and wrote core.bare = true and the identity.
The bare flag was reverted by hand that night; the identity stayed until
2026-10-06.

Pinning the hook to a test binary was the trigger, and it is gone. The
exposure was not: running the suite from any hook, or from `rebase --exec`,
still did it. Run inside a pre-commit hook in a decoy repository's linked
worktree, four packages landed 167 test commits in the decoy, rewrote its
main branch, removed the worktree's own branch, installed gx's lifecycle
hooks, and set core.bare, user.name, a fake origin and gx.enabled = false
in its config.

gxtest.DetachFromEnclosingGit unsets what git uses to pin a command to one
repository (the `git rev-parse --local-env-vars` list), plus the commit
identity and date that git hands commit hooks. Every package whose test
binary can run git now calls it first in TestMain. That is the process, not
the test helpers, because the code under test spawns git too: the hooks in
the decoy came from cli.NewRoot's auto-init in cmd/gx, whose tests never
mention git.

Three tests hold it:
- TestGitHelpersIgnoreAnEnclosingRepository re-runs the gxtest binary with
  an enclosing worktree's GIT_DIR, GIT_INDEX_FILE and identity inherited at
  process start, and fails if anything its fixtures do lands there. Without
  the call it fails with core.bare flipped to true.
- TestEveryPackageThatRunsGitDetachesFromTheEnclosingGit follows the module's
  import graph and fails for any package whose test binary links git-running
  code without the call.
- TestEnclosingGitEnvCoversGitsOwnList pins the list to the installed git.

gx review's static tools already build go test's environment from an
allowlist with no GIT_* in it; TestStaticToolChildEnvDropsTheEnclosingGitRepository
pins that, since gx review itself can run from a hook.

After the change, the full suite run inside the decoy's hook passes all 37
packages and leaves the decoy's config, hooks and refs untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

gx: https://gx.run/r/8tBYMh29iabqB-WdB0MyJg

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant