Skip to content

Worker: a stacked branch's change is rebuilt file by file, so the pull request step never stops on it - #475

Merged
nambok merged 1 commit into
mainfrom
stacked-by-files
Nov 4, 2026
Merged

nambok merged 1 commit into
mainfrom
stacked-by-files

Conversation

@nambok

@nambok nambok commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

A run whose branch repeated another open pull request's commits (that pull request's branch was rebased after the run started on it) still stopped before opening its pull request after #453. Both recovery paths had a wall:

  • Replaying the branch's own commits gave up with "too many commits to replay": the replay refused more than 100 commits (counted before the copies were dropped) and any compare GitHub lists past 250 commits.
  • The merge fallback called the conflict step, which returns "nothing merged" without an error whenever the model's merge of a file fails its checks; the flatten step then required the other branch to already be merged in ("isn't merged into the branch yet") and stopped.

The pull request step now rebuilds the branch's own change file by file as one commit on the other branch's head, so it can't hit either wall, and the run never stops without a pull request except for a missing GitHub App permission.

Changes

  • passes/restack.rs (new): the rebuild.
    • The own change is the set of paths touched by the branch's own commits (copies found by patch id and merge commits left out; a rename counts as a removal plus an addition), each with its final content on the branch, or its removal.
    • One commit on the other branch's head through the git data API: that branch's tree with those paths laid over it. No replay, no commit limit, nothing merged first.
    • A path the other branch also changed since the point the run copied from goes to the model, which merges the two versions and keeps both sides (pr::merge_file, pulled out of resolve_conflicts with the same checks).
    • When the model can't merge it: the commit keeps the other branch's version, and a labelled follow-up commit puts this change's back. The pull request opens as a draft whose stack section names the path. A marker keeps it a draft, and the squash before opening is skipped so the follow-up commit stays visible.
    • The check before moving: every path of the change holds the expected content, and every other path equals the other branch's head.
    • The move uses a lease. If someone pushed meanwhile, it is rebuilt from the new head (3 tries).
    • Each GitHub call is retried with backoff. Only a permission refusal stops the run; it names the permission ("Contents: read and write", or "Workflows: read and write" for a workflow file). Any other failure still opens the pull request, against the original base, as a draft that says why.
  • passes/stack.rs
    • before_pr and at_start use the rebuild.
    • at_start now also runs for a branch with its own open pull request, so a retry is rebuilt before implementation.
    • The copy check reads every commit (it used to stop at 60).
    • Removed recover, drop_copies, merge_and_flatten and refusal.
  • clients/github.rs
    • Added git data calls: blobs, trees, commits, tree reads, lease move, every page of a compare, and a commit's files with renames.
    • Removed rebase_onto, flatten_onto, onto_steps and onto_holds, which nothing else used. rebase_branch (update-branch requests) is unchanged.
  • Force push to a branch other pull requests stack on: the gateway's push handler sends a new restack worker message.
    • The worker rebuilds CoderHelm's open pull requests based on that branch, at most 5, one at a time.
    • Their own work starts at the push's old head.
    • Each one's stack section is updated. One that can't be rebuilt says so, and is rebuilt on its ticket's next run.
  • Draft holds (main.rs, resume::draft_hold): a stack section that asks for a look keeps the pull request a draft.

Verification

  • cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings: clean.
  • cargo test -p worker -p gateway -p common: all pass (worker 1203, common 1418, gateway 725).
  • The new tests run the rebuild against an in-memory repository (acme/web, coderhelm/feature-a as fix(worker): make Jira repo selection smart via code evidence #42, coderhelm/feature-b stacked on it):
    • a branch with 150 own commits plus copies, past the old 100 limit, stacks as one commit;
    • a dependency that moved on and was never merged into the branch, plus a merge of main into the branch, stacks;
    • a path both changed is merged by the model, which is given the copied-from version;
    • a merge that fails gives the dependency's version plus a labelled follow-up commit, and a draft hold in the section;
    • removals and renames carry over;
    • a kept branch rebuilt at start leaves no copies for the work or the pull request step, and a second rebuild is stable;
    • a force-pushed dependency rebuilds from its old head;
    • a push during the rebuild is built again and included;
    • a 502 three times in a row is retried; a 403 "Resource not accessible by integration" stops with the permission and moves nothing;
    • only CoderHelm's same-repo pull requests stacked on the pushed branch are rebuilt, at most 5;
    • only a force push to a branch other than the default (not a new branch, a tag or a plain push) asks for a rebuild;
    • the worker message contract lists restack.

…l request step never stops on it

A run whose branch repeated another open pull request's commits still
stopped before its pull request: the commit replay gave up past its
commit limit, and the merge fallback required that branch to already be
merged in, which it wasn't.

- The branch's own change (the files its own commits touched, copies and
  merge commits left out) becomes one new commit on the other branch's
  head with the git data API: that branch's tree with those paths laid
  over it, removals and renames included. No commit is replayed, so the
  commit count never matters, and nothing has to be merged first.
- A file both changed since the point the run copied from is merged by
  the model, keeping both sides. When it can't be, the commit keeps the
  other branch's version and a labelled follow-up commit puts this
  change's back; the pull request opens as a draft that names the file.
- Before the branch moves (with a lease, rebuilt when it moved), every
  path of the change must hold its content and every other path the
  other branch's. GitHub errors are retried with backoff; only a missing
  App permission stops the run, and the message names it. Any other
  failure still opens the pull request, as a draft that says why.
- A kept or retried branch is rebuilt the same way before implementation,
  and a force push to a branch CoderHelm's open pull requests stack on
  rebuilds them (at most five, one at a time).
- The replay and merge-then-flatten steps (rebase_onto, flatten_onto)
  are gone.
@nambok
nambok merged commit 61deedf into main Nov 4, 2026
10 checks passed
@nambok
nambok deleted the stacked-by-files branch November 4, 2026 23:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant