docs(git-ops): add push rejection and merge-overwrite recovery protocol - #287
Open
Joi Ito (Joi) wants to merge 1 commit into
Open
docs(git-ops): add push rejection and merge-overwrite recovery protocol#287Joi Ito (Joi) wants to merge 1 commit into
Joi Ito (Joi) wants to merge 1 commit into
Conversation
`agents/git-ops.md` had no guidance for the most common way a git operation fails mid-task. Grepping the agent for reject, non-fast-forward, fetch-first, stash, recover, diverge, or force-with-lease returned zero matches, and Remote Operations offered only `git pull --rebase` and `git push -u origin <branch>`. With no protocol, a rejected push had two bad outcomes: hand the raw `! [rejected] ... (non-fast-forward)` back to the caller as a failure, or reach for `--force` and overwrite remote work. Adds a Push Rejection Recovery section, deliberately structured so that no recovery command appears before the check that decides whether it is safe: - Step 1 fetches, THEN counts with `git rev-list --left-right --count HEAD...origin/<branch>`. The local `origin/<branch>` ref is stale until the fetch, so counting first reads the wrong data. - Step 2 is a case table. Remote-only ahead goes to recovery. Both sides non-zero stops. - Step 3 holds the recovery, with the stash conditional on a dirty tree and `git stash pop` conditional on having stashed. Divergence stops rather than auto-recovering, and that is the deliberate design choice in this change. Two different situations produce an identical commit graph: someone else pushed while you worked (rebase is correct), or you amended/squashed/rebased already-pushed commits (rebase replays your rewritten versions over their originals and publishes both). The graph cannot tell them apart -- matching subjects are a hint, not proof, since an amend can change the subject and two people can write the same one. The distinguishing information is whether *you* rewrote those commits, which is the caller's knowledge, not the agent's. So the protocol shows the divergence with `git log --oneline --left-right` and stops. An agent guessing wrong here publishes duplicate history to a shared branch, and over-blocking is cheap. Remote and branch are named explicitly throughout, including in the `--force-with-lease` escape hatch: a rejected `git push -u` never established upstream tracking, so a bare `git pull --rebase` has nothing to rebase onto and `git status -sb` has no ahead/behind to report. Also covered: stop and report on rebase conflict rather than guessing (and never `git rebase --skip`, which silently discards a commit); stop BEFORE pushing if `git stash pop` conflicts, since the rebase can succeed while the caller's pre-existing uncommitted work collides with what was just integrated; and the distinct "local changes would be overwritten" working-tree case. Force-push guidance lives in exactly one place -- the divergence paragraph -- so there is no second rule to contradict it. The Git Safety Protocol lists get "rebase or force-push a diverged branch" under NEVER-without-explicit-request, and "classify a rejected push before recovering it" plus "stop before pushing if restoring a stash conflicted" under ALWAYS. Frontmatter Authoritative-on now lists push rejection so callers route these failures here. The four thin agent copies get one compressed rule rather than the full section, matching their numbered-rule format, but carrying every load-bearing element: fetch before count, stop on divergence, conditional stash pop, stop-on-conflict, no force-push on the agent's own initiative, and never on main/master. This includes the two under experiments/, which are installable runtime surfaces -- their READMEs document `amplifier bundle add ...` and their bundle entrypoints reference `<bundle>:git-ops`. Closes microsoft/amplifier#288 Generated with Amplifier Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.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.
Problem
agents/git-ops.mdhad no guidance for the most common way a git operation fails mid-task. Grepping the agent forreject,non-fast-forward,fetch first,stash,recover,diverge, orforce-with-leasereturned zero matches, and Remote Operations offered only two lines:With no protocol, a rejected push had two bad outcomes: hand the raw
! [rejected] ... (non-fast-forward)back to the caller as a hard failure, or reach for--forceand overwrite remote work.Reported in microsoft/amplifier#288.
Approach
Adds a Push Rejection Recovery section, structured so no recovery command appears before the check that decides whether it is safe.
git rev-list --left-right --count HEAD...origin/<branch>. The localorigin/<branch>ref is stale until the fetch, so counting first reads the wrong data.git stash popconditional on having stashed.Also covered: stop and report on rebase conflict rather than guessing (and never
git rebase --skip, which silently discards a commit); stop before pushing ifgit stash popconflicts; and the distinct "local changes would be overwritten" working-tree case, which is a working-tree problem rather than a history problem.The design decision worth reviewing: divergence stops
A diverged branch does not auto-recover. That is deliberate, and it is the part I would most like a second opinion on.
Two different situations produce an identical commit graph:
git pull --rebasereplays your rewritten versions on top of them and publishes both, duplicating history.The graph cannot tell these apart. I tried two mechanical classifiers and both were unsound: matching commit subjects are a hint rather than proof, since an amend can change the subject and two people can independently write the same one. The information that actually distinguishes the cases — whether you rewrote those commits — is the caller's knowledge, not the agent's.
So the protocol shows the divergence with
git log --oneline --left-right HEAD...origin/<branch>and stops. If the caller confirms the remote commits are older copies of work they rewrote, the resolution isgit push --force-with-lease origin <branch>— named explicitly, on a branch they own, never onmain/master, never on the agent's own initiative.Over-blocking is cheap here. An agent guessing wrong publishes duplicate history to a shared branch.
Consistency notes
--force-with-leaseescape hatch — a real internal contradiction in executable guidance.git push -unever established upstream tracking, so a baregit pull --rebasehas nothing to rebase onto andgit status -sbhas no ahead/behind to report.Authoritative on:now lists push rejection so callers route these failures here.Scope: which copies changed
git-ops.mdexists in five places. The four thin variants get one compressed rule rather than the full section, matching their numbered-rule format, but carrying every load-bearing element: fetch before count, stop on divergence, conditionalstash pop, stop-on-conflict, no force-push on the agent's own initiative, never on main/master.agents/git-ops.mdbundles/anchors/agents/git-ops.mdbundles/anchors-amp-dev/agents/git-ops.mdexperiments/behavioral-anchor/agents/git-ops.mdexperiments/behavioral-anchor-amplifier-dev/agents/git-ops.mdThe two under
experiments/are included because they are installable runtime surfaces, not inert samples — their READMEs documentamplifier bundle add 'git+https://github.com/microsoft/amplifier-foundation@main#subdirectory=experiments/...'and their bundle entrypoints reference<bundle>:git-ops. Happy to drop them from this PR if you would rather keep experiments frozen.How to verify
This is prompt/documentation content, so there is nothing to run — existing tests cover frontmatter and metadata shape, not runbook semantics. Review is the verification. The specific claims to check are:
git rev-list --left-right --count HEAD...origin/<branch>aftergit fetch origincorrectly reports<local-only> <remote-only>.git stash popretains the stash on conflict (so the "nothing is lost yet" claim holds).git push --force-with-leaserefuses when the remote moved after your fetch.This went through five rounds of independent review; the divergence-stops design is where it landed after two unsound classifier attempts were rejected. If you would prefer the protocol attempt classification anyway, say so and I will rework it — but I could not find a sound test.