Enforce the configured git identity on every commit#72
Merged
brycelelbach merged 1 commit intoJun 7, 2026
Merged
Conversation
Agents routinely ignore the global git author, email, and signing key the bootstrap configures, committing under their own identity via `git -c user.email=...`, `git commit --author=...`, GIT_AUTHOR_*/GIT_COMMITTER_* env vars, or a repo-local `git config user.email`. Add two layers that keep commits on the configured identity. write_agent_git_rules writes a managed block to each harness's global instruction file — ~/.claude/CLAUDE.md (Claude Code) and ~/.codex/AGENTS.md (Codex), both loaded in every repository — telling the agent to always commit with the configured identity and leave the global git config alone. install_git_hooks makes the rule non-optional. It writes a dispatcher to ~/.aab/git-hooks/aab-git-hook, symlinks it under each managed hook name, and points the global core.hooksPath at it. On pre-commit the dispatcher compares the resolved author and committer identity (git var GIT_AUTHOR_IDENT / GIT_COMMITTER_IDENT — which reflect --author and the env-var overrides that the effective `git config user.email` does not) against the global user.name / user.email (read from --global, which -c / env / repo-local config cannot poison) and rejects mismatches. When global signing is configured it also rejects commits that disable signing via config or swap the signing key. The dispatcher chains through to the repository's own hook of the same name, because a global core.hooksPath replaces rather than supplements .git/hooks. The deliberate per-commit escape hatches git provides (--no-verify, the --no-gpg-sign flag) are left intact, and the hook is a no-op when no global identity is configured. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
The bootstrap configures a global git author, email, and (optionally) a signing key, but unattended agents constantly ignore it — they commit under their own identity via
git -c user.email=...,git commit --author=...,GIT_AUTHOR_*/GIT_COMMITTER_*env vars, or a repo-localgit config user.email. This adds two layers that keep commits on the configured identity:(0) An agent rule in every harness's global instruction file.
write_agent_git_ruleswrites a managed block to~/.claude/CLAUDE.md(Claude Code user memory, loaded in every repo) and~/.codex/AGENTS.md(Codex global instructions, loaded fromCODEX_HOMEin every repo), telling the agent to always commit with the configured identity and leave the global git config alone. The block uses the standard# >>> autonomous-agent-bootstrap >>>markers, so re-runs replace it in place and any pre-existing content in those files is preserved.(1) A global git hook that enforces it.
install_git_hookswrites a dispatcher to~/.aab/git-hooks/aab-git-hook, symlinks it under each managed hook name, and points the globalcore.hooksPathat that directory. Onpre-committhe dispatcher:git config --global --get user.name/user.email— immune to-c,GIT_CONFIG_PARAMETERS, env, and repo-local overrides;git var GIT_AUTHOR_IDENT/GIT_COMMITTER_IDENT— which do reflect--authorandGIT_AUTHOR_*/GIT_COMMITTER_*env vars that the effectivegit config user.emaildoes not;commit.gpgsign=true), also rejects commits that disable signing via config (-c commit.gpgsign=false) or swap the signing key.Because a global
core.hooksPathreplaces rather than supplements a repo's.git/hooks, the dispatcher chains through to the repository's own hook of the same name (located via--git-common-dir, so worktrees and thecore.hooksPathself-reference are handled) after its checks pass — projects that ship Husky /pre-commit/ lint-staged hooks keep working.Scope is intentionally limited to identity and signing. The deliberate per-commit escape hatches git provides —
git commit --no-verify(skips hooks) and the--no-gpg-signflag (skips signing) — are left intact. When no global identity is configured, the hook is a no-op and all commits pass.Design notes
bootstrap.bashviaemit_git_hook_script(a quoted heredoc) so the script stays self-contained for thecurl ... | bashinstall path.test.bash --lintextracts andshellchecks the emitted hook separately, since shellcheck can't see inside the heredoc.--git-path hooksis deliberately not used to find the repo-local hook: it honorscore.hooksPathand would resolve back to the dispatcher (infinite self-exec).--git-common-dir/hooksis used instead, plus a symlink-identity guard.Test plan
./test.bash(lint + unit)108/108 pass. (The trailing
BW01advisory is from the pre-existingload_config_file aborts on malformed inputtest, unrelated to this change.) 17 new unit tests cover hook install + symlinks +core.hooksPath, emitted-hook validity, idempotency, the full identity-override matrix (-c,--author,GIT_AUTHOR_EMAIL,GIT_COMMITTER_EMAIL, repo-local config),--no-verifypass-through, the no-global-identity no-op, repo-hook chaining (pass + fail), signing-disabled block, and the rule-file managed block (write / idempotent / preserve)../test.bash --docker(full e2e in a freshubuntu:22.04container)Run from the main checkout (not a worktree),
GITHUB_TOKENforwarded. Exit 0. New assertions (12b/12c/12d) passed in both idempotency runs:./test.bash --secrets(gitleaks v8.18.4)./test.bash --e2e— covered by--dockerabove, which runs--e2einside the container against a pristine$HOME. Not run a second time directly on this host to avoid mutating the host's real~/.gitconfig/core.hooksPath../test.bash --smoke— N/A for this change: it spends live inference and exercises provider wrappers, which this PR does not touch.🤖 Generated with Claude Code