chrismdp / rebase-conflicting-pull-requests

A pull request that conflicts with main is rebased onto it

Public ·
Version 3 of 4
  1. #4 · chrismdp · · 0.8Latest
  2. #3 · chrismdp · · 0.4Showing
  3. #2 · chrismdp · · 0.4
  4. #1 · chrismdp · · 0.3
Fork

workflow.yaml

workflow.yaml is AWML 0.4, as it was pushed. The registry draws, checks and fits it converted to AWML 0.8.

# A pull request that conflicts with main is rebased onto it. The repository is public, so
# everything sits in one scope: anyone may read the pull requests and their branches. What matters
# is who may act. Only branches in the repository itself, opened by its own people or by the build
# workflow, are touched, so strangers' text never reaches the draft step. Claude rebases with a
# read-only token and hands back the rebased branch. A separate step checks it and replaces the
# branch only if nobody has pushed to it meanwhile. The maintainer still reviews and merges the pull
# request, outside this workflow. The repository's app pushes the branch, so GitHub runs CI on it
# as it would on a person's push, and main's rule holds the merge until CI passes.
#
# The actions implementation runs it on GitHub Actions. Finding the conflicting pull requests runs
# in one job and Claude rebases in a second. The check, the push and the comment run in a third job
# on a fresh runner, which shares none of the second's access, so Claude can't write the verdict the
# push relies on.
awml: "0.4"
title: A pull request that conflicts with main is rebased onto it
scopes:
  - { id: public, label: "public: anyone who reads the repository", processing: any }
parts:
  - { id: landed, kind: trigger, label: "A commit lands on main", wait: PT1H }
  - { id: token, kind: connector, label: "The repository, through its own Actions token", in: public }
  - { id: app, kind: connector, label: "The repository, through its own GitHub App", in: public }
  - { id: find, kind: code, op: bring-in, label: "Bring in the open pull requests that conflict with main", in: public }
  - { id: whose, kind: code, op: sort, label: "Sort by rule: is the branch in this repository, opened by its own people or the build workflow?", in: public }
  - { id: repo, kind: context, label: "The branch's commits, main, and AGENTS.md", in: public }
  - { id: rebase, kind: ai, op: draft, label: "Rebase the branch onto main, keeping both sides of each conflict", in: public }
  - { id: bundle, kind: context, label: "The rebased branch, as a bundle", in: public }
  - { id: gate, kind: code, op: check, label: "Check it sits on main, has no conflict markers or workflow changes, and passes every check", in: public }
  - { id: push, kind: code, op: do, label: "Force-push the rebased branch, only if it hasn't moved", in: public }
  - { id: replace, kind: outside, label: "Replace the pull request's branch: Allow", in: public }
  - { id: tell, kind: code, op: do, label: "Comment what was resolved, or why it couldn't be", in: public }
  - { id: thread, kind: surface, label: "The pull request's thread", in: public }
  - { id: strangers, kind: comment, label: "A fork's pull request, or one opened by a stranger, is never touched, so every line Claude reads was written by the repository's own people. AWML can't mark this yet (awml #5)" }
  - { id: held, kind: comment, label: "The draft step's token can only read, so it can't push. The push names the commit the bring-in step saw, so GitHub refuses it if anyone pushed meanwhile (awml #26)" }
  - { id: undo, kind: comment, label: "Allow, because the old commit stays on GitHub and the comment names it, so the branch can be put back with one push" }
  - { id: ci, kind: comment, label: "GitHub starts CI from the app's push, though not from one by the Actions token, so CI runs on the rebased branch and shows on the pull request. The app's key is a secret only the push step is given" }
lines:
  - { from: landed, to: find, kind: start, label: "the push" }
  - { from: token, to: find, kind: data, label: "open pull requests and whether each conflicts" }
  - { from: find, to: whose, kind: start, label: "[for each that conflicts]" }
  - { from: whose, to: rebase, kind: start, label: "[the repository's own]" }
  - { from: repo, to: rebase, kind: data }
  - { from: rebase, to: bundle, kind: data }
  - { from: bundle, to: gate, kind: data }
  - { from: gate, to: push, kind: start, label: "[all pass]" }
  - { from: bundle, to: push, kind: data }
  - { from: push, to: replace, kind: data }
  - { from: replace, to: app, kind: data }
  - { from: push, to: tell, kind: start, label: "[pushed or refused]" }
  - { from: gate, to: tell, kind: start, label: "[any fail]" }
  - { from: rebase, to: tell, kind: start, label: "[gave up]" }
  - { from: tell, to: thread, kind: data }
  - { from: strangers, to: whose, kind: tie }
  - { from: held, to: push, kind: tie }
  - { from: undo, to: replace, kind: tie }
  - { from: ci, to: app, kind: tie }
implementations:
  actions:
    label: GitHub Actions
    blocks:
      - { id: github, label: "GitHub: pull requests, branches, comments", parts: [landed, token, app, repo, bundle, replace, thread] }
      - { id: findjob, label: "Actions job: find, with a read-only token", platform: actions-code, parts: [find, whose] }
      - { id: draftjob, label: "Actions job: rebase, with a read-only token", platform: actions-code, parts: [] }
      - { id: agentjob, label: "Claude Code, claude-opus-5-5, on the maintainer's subscription", platform: actions-agent, in: draftjob, parts: [rebase] }
      - { id: codejob, label: "Actions job: check, push and comment", platform: actions-code, parts: [gate, push, tell] }