Until 2026-10-06, a push onto a protected branch by someone who did not own it was a dead end. The push was refused, which was right, but the obvious way out was refused too, and only a fresh clone recovered. Now the refusal says what to do, and pushing again keeps the work.
What happens now
Say trunk is protected and you do not own it, and you commit on trunk by mistake and push:
- The push is refused, and the refusal names where the work will go:
check-in 9e1c5d97f9be would join protected branch 'trunk'. Your next push ... keeps it on branch attempt/<you>/9e1c5d97f9be. - Push again. The check-in lands on
attempt/<you>/9e1c5d97f9be. The move is written in the same commit as the push, so trunk never shows it, not even for a moment. - quarry opens a landing from that branch to trunk, as you, titled from your check-in. It waits in your landings for an owner to accept it.
The server's half is zit 0.3.38 (zit#209): the move is decided before anything is written, and the front tells zit who authenticated, since the pusher's own name in the check-in can be forged. quarry's half (!849) sends that login and opens the landing from zit's report of what it moved. It was checked on the live forge with a throwaway repository: refused once, kept on the attempt branch the second time, trunk untouched, and the landing opened.
zit 0.3.38 also closed five hand-crafted ways past branch protection; none is something zit or fossil writes in ordinary use.
Two related fixes
- Anonymous
fossil syncworks for readers (!821). Fossil's sync always carries a push card, even with nothing to push, so a signed-outfossil sync -uused to be refused whole. It is now served as a pull, while any write card still needs a verified writer. - The agent guide names the right password (!848).
llms.txttold agents to push with the account password, which pushes refuse. It now points at the push credential on the account page (/me#push), and tells an agent to ask for it rather than make a new one, since a new one retires the old.