Skip to content

fix: use --force-with-lease to abort stale dev branch pushes#401

Merged
0x46616c6b merged 5 commits intomainfrom
fix/force-with-lease-against-stale-pushes
May 5, 2026
Merged

fix: use --force-with-lease to abort stale dev branch pushes#401
0x46616c6b merged 5 commits intomainfrom
fix/force-with-lease-against-stale-pushes

Conversation

@0x46616c6b
Copy link
Copy Markdown
Contributor

@0x46616c6b 0x46616c6b commented Apr 29, 2026

Type of Change

  • Bugfix
  • Enhancement / new feature
  • Refactoring
  • Documentation

Description

Multiple AutoDev runs can race: each one captures origin/<base>, builds a dev tip from base + labeled PRs, and ends with git push -f. When a slower run finishes after a newer one, its stale build silently overwrites the fresher tip. Observed downstream as a Flux-driven deployment rolling forward → back → forward within minutes, while git log showed only a single forward step.

This PR replaces the unconditional git push -f with git push --force-with-lease=refs/heads/<branch>:<sha>, where <sha> is origin/<branch> snapshotted at the start of the run (capturing it right before the push would defeat the guard). Behavior on push:

  • Lease rejected (concurrent run won the race): logged as a warning, the run exits green. A subsequent AutoDev run reconciles from a fresh state.
  • Other push failures: surfaced via setFailed with the captured stderr, instead of being swallowed by ignoreReturnCode: true like before.
  • PR comments and labels: deferred until after the push succeeds, so a rejected push no longer leaves "merged" comments/labels on PRs whose merges never landed on the branch.

Recommended complementary mitigation in consumer workflows: a concurrency group with cancel-in-progress: true. The lease check here is defense in depth for consumers that forget it.

Checklist

  • Write tests
  • Make sure all tests pass
  • Update documentation
  • Review the Contributing Guideline and sign CLA
  • Reference relevant issue(s) and close them after merging

The changes and the PR were generated by Claude.

@0x46616c6b 0x46616c6b marked this pull request as ready for review April 29, 2026 13:13
@0x46616c6b 0x46616c6b requested a review from a team as a code owner April 29, 2026 13:13
@0x46616c6b 0x46616c6b requested review from axdotl, Copilot and flaxel April 29, 2026 13:13
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the AutoDev action against stale overwrites of the dev branch when multiple workflow runs push concurrently, by switching from unconditional force-push to a lease-guarded force-push that fails if the remote tip moved.

Changes:

  • Replace git push -f with git push --force-with-lease=refs/heads/<branch>:<observed-sha> and explicitly fail the action if the push is rejected.
  • Add Vitest coverage to assert the lease-pinned push behavior and the failure path on push rejection.
  • Update the compiled dist/index.js bundle to reflect the new push logic.

Reviewed changes

Copilot reviewed 2 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/autodev.ts Uses --force-with-lease and fails the run when the remote tip changes during execution.
src/autodev.test.ts Adds tests validating the new lease-pinned push and the rejected-push failure behavior.
dist/index.js Updates the distributed build output to match the source changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/autodev.ts
Copy link
Copy Markdown
Contributor

@flaxel flaxel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you tested it with a simple repo to see if the new implementation works as expected. 🎉

0x46616c6b added a commit that referenced this pull request Apr 29, 2026
merge() previously posted success comments and labels at the end of
its body, before autoDev() ran the push step. With force-with-lease
now able to abort the push when origin/<branch> moved during the run,
we would still leave "merged successfully" comments and the
successful label on PRs whose merges never landed on the branch.

Move the comment/label calls out of merge() into autoDev() and run
them only after the push step has completed without rejection.
merge() now returns {message, success} so the caller still has the
information it needs.

Addresses Copilot review feedback on PR #401.

Co-Authored-By: Claude <claude@anthropic.com>
@0x46616c6b 0x46616c6b requested a review from Copilot April 29, 2026 13:49
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to prevent stale AutoDev workflow runs from overwriting a newer dev branch tip when multiple runs execute concurrently, by replacing an unconditional force-push with a force-with-lease guarded push and by failing the action on push rejection.

Changes:

  • Replace git push -f with git push --force-with-lease=... and fail the action when the push is rejected.
  • Defer PR comments/labels updates until after the push step completes successfully.
  • Add/extend Vitest coverage for the new push behavior and the “do not comment/label on rejected push” behavior.

Reviewed changes

Copilot reviewed 2 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/autodev.ts Switches push strategy to force-with-lease, adds failure handling, and defers comment/label updates until after push success.
src/autodev.test.ts Adds tests around the new push command, rejection handling, and ordering of push vs. comment/label updates.
dist/index.js Updates the built distribution output to reflect the source changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/autodev.ts
Comment thread src/autodev.test.ts
Comment thread src/autodev.ts
0x46616c6b and others added 2 commits May 4, 2026 14:20
Multiple AutoDev workflow runs can execute in parallel — each captures
origin/<base> at its own start time, builds the dev tip from base +
labeled PRs, and ends with `git push -f`. If a slower run finishes
after a newer one, the slower run's stale snapshot overwrites a
fresher dev tip. Concrete observation in a downstream repository: a
Kubernetes deployment was rolled forward, then back, then forward
again within ~3 minutes, although git history showed only a single
forward step — the "back" was a transient state caused by an older
AutoDev push landing after a newer one.

This commit replaces the unconditional force-push with a
force-with-lease pinned to the SHA of origin/<branch> observed right
before the push. If a concurrent run has updated the branch tip in
the meantime, the push fails and the action calls setFailed instead
of silently overwriting; a subsequent trigger then rebuilds the
branch from a fresh state.

Two new tests cover the lease argument and the setFailed path. The
prebuilt dist/ is rebuilt via `pnpm run package`.

Co-Authored-By: Claude <claude@anthropic.com>
merge() previously posted success comments and labels at the end of
its body, before autoDev() ran the push step. With force-with-lease
now able to abort the push when origin/<branch> moved during the run,
we would still leave "merged successfully" comments and the
successful label on PRs whose merges never landed on the branch.

Move the comment/label calls out of merge() into autoDev() and run
them only after the push step has completed without rejection.
merge() now returns {message, success} so the caller still has the
information it needs.

Addresses Copilot review feedback on PR #401.

Co-Authored-By: Claude <claude@anthropic.com>
@0x46616c6b 0x46616c6b force-pushed the fix/force-with-lease-against-stale-pushes branch from bd7d5de to 9211c74 Compare May 4, 2026 12:21
0x46616c6b and others added 3 commits May 5, 2026 10:44
Co-Authored-By: Claude <claude@anthropic.com>
The previous --force-with-lease guard captured origin/${branch} immediately
before the push, after a fresh `git fetch`. That allowed a stale run to adopt
the newer remote tip as its lease expectation and overwrite it anyway, which
defeats the guard. The lease SHA is now snapshotted right after the initial
fetch (or set to the SHA we pushed when the branch is created during the run),
so a remote that moves while we work causes the lease to fail.

The push failure path now distinguishes lease rejection from other failures
(auth, network, protected branch, …) by inspecting captured stderr, instead of
always claiming origin/${branch} moved.

Co-Authored-By: Claude <claude@anthropic.com>
…nt run

Lease rejection means another AutoDev run won the race and produced a fresh
${branch} tip — the workflow that pushed last has already done the work, and a
subsequent AutoDev run will reconcile anything that landed afterwards. That is
expected behavior, not a failure, so a red ✗ on the run is misleading and
generates avoidable failure notifications.

The lease-rejected path now uses warning() so the run stays green while still
surfacing the skip in the job summary. Genuine push failures (auth, network,
protected branch, …) keep using setFailed().

Co-Authored-By: Claude <claude@anthropic.com>
@0x46616c6b 0x46616c6b added the enhancement New feature or request label May 5, 2026
@0x46616c6b 0x46616c6b merged commit 77e84c1 into main May 5, 2026
11 checks passed
@0x46616c6b 0x46616c6b deleted the fix/force-with-lease-against-stale-pushes branch May 5, 2026 09:05
@github-actions github-actions Bot locked and limited conversation to collaborators May 5, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants