Skip to content

Fail Closed on a Source-Only Push After an Upstream Change in the Same Command in the Write Guard #3322

Closed

Description

@ptr727

Defect

Under push.default=upstream, Git sends a source-only refspec such as git push origin feature/example to the branch named by branch.feature/example.merge. The write guard reads that mapping from the persisted config, but a command that changes the upstream earlier in the same tool call is not seen. The guard then reads the old mapping, falls back to the literal source branch, and allows a push that Git sends to a ruled branch.

Raised by CodeRabbit on the promotion pull request #3321 (host-setup/agent-safety/claude/gh-write-guard.py, the source-only mapping around _mapped_src_branches and the command classification _head_may_move uses).

Reproduction

Constructed repository: origin at example/alpha, where any release/ branch requires a pull request. The checkout is on feature/example, its upstream is origin/feature/example, and push.default is upstream. Each command was classified by the guard at main and at develop:

main   develop  command
allow  deny     (control) persisted merge = release/1, then: git push origin feature/example
allow  allow    (control) persisted merge = feature/example, then: git push origin feature/example
allow  allow    git branch -q -u origin/release/1 feature/example && git push origin feature/example
allow  allow    git branch -q --set-upstream-to=origin/release/1 && git push origin feature/example
allow  allow    git switch -q -c feature/new --track origin/release/1 && git push origin feature/new
allow  allow    git checkout -q -b feature/new -t origin/release/1 && git push origin feature/new
allow  allow    git config branch.feature/example.merge refs/heads/release/1 && git push origin feature/example

Git sends each of the last five pushes to release/1. Main allows all of them too, so this is a gap the two branches share, not one the promotion added.

Expected

An upstream-setting option on branch, checkout, or switch (-u, -t, --track, --set-upstream-to, and their abbreviated long spellings) counts as a configuration change, the same as a config write. A source-only refspec read while the configuration is unreliable is recorded as undetermined rather than mapped to its literal source branch, so the push fails closed.

Acceptance

  • Each command in the table above is denied, and each control keeps its current result.
  • A develop-versus-branch differential over the guard's self-test cases shows no deny-to-allow flip.

Activity

  1. added
    bugSomething isn't working
    scriptA defect in hub tooling
    on Oct 21, 2026
  2. ptr727 commented on Oct 23, 2026

    @ptr727
    OwnerAuthor

    Closed as out of the write guard's scope under the maintainer's decision (ptr727, 2026-10-23) on #3391, item 1. The guard bounds a good-faith agent's mistake rather than an adversary, so a parse gap reached only by a command shape no agent plausibly types in a coding session is closed rather than fixed, and the guard's own growth is a cost each later review pays again. The scope statement lands in the guard's docstring and README under #3391. Reopen only with a command a session actually produced.

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

    bugSomething isn't workingscriptA defect in hub tooling

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions