Skip to content

test harnesses: git_init writes a test identity and disables signing into whatever dir it is handed, with no guard against the real repo #2162

Description

@kyle-sexton

Summary

Six test harnesses define a git_init() (or equivalent) that writes a throwaway identity and disables
commit signing into whatever directory it is handed:

git_init() {
  local dir="$1"
  git -C "$dir" init -q
  git -C "$dir" config user.email t@t.test
  git -C "$dir" config user.name test
  git -C "$dir" config commit.gpgsign false
  git -C "$dir" config core.autocrlf false
}

Nothing verifies that $dir is a fresh temporary repository. If the caller's mktemp -d fails, returns
empty, or a cd does not land where expected, git -C "$dir" config writes into the real repository's
.git/config instead.

Files carrying the pattern:

  • scripts/check-changelog-parity.test.sh
  • scripts/affected-tests.test.sh
  • scripts/check-changed-skills.test.sh
  • scripts/check-docs-only.test.sh
  • plugins/claude-ops/skills/plugins/scripts/fleet-state.test.sh
  • plugins/guardrails/hooks/stale-path-verify.test.sh

Observed, not hypothetical

The working clone's .git/config was found carrying exactly that trio:

user.email=t@t.test
user.name=test
commit.gpgsign=false

Local config overrides global, and every worktree of the clone inherits it. Consequences, all observed:

  • Commits came out unsigned, while main carries an active required_signatures ruleset with
    bypass_actors: [] — so the branch could not merge.
  • Commits were authored as test <t@t.test>. GitHub maps that address to no account, so even after the
    signing was corrected the commit reported verified: false, reason: no_user. A valid signature does
    not rescue an unknown author identity
    — that cost a second amend cycle to discover.
  • The guarded merge helper reports only mergeStateStatus=BLOCKED in this state, without naming
    signatures or identity, so the cause is invisible in its output and has to be found by querying commit
    verification by hand.

Two independent pull requests were blocked simultaneously by this before the cause was found.

Why it is worth fixing rather than cleaning up after

The failure is silent, it survives in the clone until someone notices, and every commit made from that
clone or any of its worktrees in the meantime is quietly unmergeable. Nothing in the normal workflow
surfaces it: git commit succeeds, the push succeeds, CI goes green, and only the merge gate refuses —
with a message that does not mention identity or signing.

Suggested fix

Guard git_init so it refuses any directory that is not a fresh throwaway:

  • fail closed on an empty or unset $dir;
  • refuse when $dir resolves inside the current repository's working tree, or when
    git -C "$dir" rev-parse --show-toplevel resolves to this repository;
  • prefer git -C "$dir" -c user.email=… -c user.name=… … per invocation over persisting config, or use
    GIT_CONFIG_GLOBAL/GIT_CONFIG_SYSTEM pointed at a scratch file, so nothing is written to any
    repository's .git/config at all.

The per-invocation form removes the whole class: a test that never writes config cannot pollute a
repository, however its temp-dir handling fails.

Whichever shape is chosen, apply it to all six call sites — this is a shared idiom, and fixing one leaves
the others live.

Not established

Which specific invocation polluted this clone, and when. The config was found already present and has been
removed; no reproduction of the failing temp-dir path was attempted, so the mechanism above is derived
from the code rather than observed in the act. The pattern and the resulting config match exactly, but
that is correlation, and the guard is worth adding regardless of which caller did it.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions