Summary
gh stack sync can fetch and rebase a stack successfully, then fail during the final force push because the push uses a bare --force-with-lease with branch names instead of explicit destination refs and lease SHAs.
This is most visible in checkouts where local branch upstream/tracking configuration is missing, stale, or does not line up with the branches that gh stack sync just fetched.
Problem
Today the push shape is effectively:
git fetch origin b1
git rebase ...
git push origin --force-with-lease --atomic b1
The failure happens at the final git push. Git has to infer both the destination branch and the expected lease value from local branch configuration / remote-tracking refs. If that inference does not match the refs that gh stack sync just fetched, the push can be rejected even though the command already refreshed the relevant stack branch.
Expected Behavior
After gh stack sync fetches stack branches, the subsequent force push should use the fetched local tracking refs as the lease source.
For example:
git fetch origin b1
git rebase ...
git rev-parse --verify --quiet refs/remotes/origin/b1
git push origin --force-with-lease=refs/heads/b1:<fetched-sha> --atomic b1:refs/heads/b1
If someone updates b1 after the fetch, the push should still fail safely because refs/heads/b1 no longer equals <fetched-sha>.
If b1 did not have a local tracking ref, the lease should use an empty expected value, so creating the remote branch still fails if someone created it first.
Proposed Fix
Build explicit per-branch force-with-lease arguments:
--force-with-lease=refs/heads/<branch>:<tracking-ref-sha>
and push with explicit local-to-remote refspecs:
<branch>:refs/heads/<branch>
This avoids relying on Git's branch/upstream inference while preserving the stale-ref safety guarantee of --force-with-lease.
Related PR
I have a fix in my fork for reference ravi-pplx#1
Summary
gh stack synccan fetch and rebase a stack successfully, then fail during the final force push because the push uses a bare--force-with-leasewith branch names instead of explicit destination refs and lease SHAs.This is most visible in checkouts where local branch upstream/tracking configuration is missing, stale, or does not line up with the branches that
gh stack syncjust fetched.Problem
Today the push shape is effectively:
The failure happens at the final
git push. Git has to infer both the destination branch and the expected lease value from local branch configuration / remote-tracking refs. If that inference does not match the refs thatgh stack syncjust fetched, the push can be rejected even though the command already refreshed the relevant stack branch.Expected Behavior
After
gh stack syncfetches stack branches, the subsequent force push should use the fetched local tracking refs as the lease source.For example:
If someone updates
b1after the fetch, the push should still fail safely becauserefs/heads/b1no longer equals<fetched-sha>.If
b1did not have a local tracking ref, the lease should use an empty expected value, so creating the remote branch still fails if someone created it first.Proposed Fix
Build explicit per-branch force-with-lease arguments:
--force-with-lease=refs/heads/<branch>:<tracking-ref-sha>and push with explicit local-to-remote refspecs:
<branch>:refs/heads/<branch>This avoids relying on Git's branch/upstream inference while preserving the stale-ref safety guarantee of
--force-with-lease.Related PR
I have a fix in my fork for reference ravi-pplx#1