The regraft GitHub Action

Runs regraft on a schedule and turns the template’s new commits into pull requests — the same shape of workflow Dependabot gives you for dependencies, but for the scaffolding.

The one you probably want

# .github/workflows/template-sync.yml
name: Template sync

on:
  schedule:
    - cron: '0 6 * * 1'   # Mondays, 06:00 UTC
  workflow_dispatch:       # and a button

permissions:
  contents: write
  pull-requests: write

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0   # regraft needs the full history, not a shallow clone

      - uses: achedon12/regraft@v1

One pull request a week, only when the template actually moved.

One pull request per template commit

      - uses: achedon12/regraft@v1
        with:
          strategy: per-commit
          open-pull-requests-limit: 3
          labels: template-sync,dependencies
          reviewers: alice,bob

Each template commit becomes its own pull request, reviewable and mergeable on its own, capped at three open at a time — exactly how Dependabot handles dependencies.

The trade-off is real and worth knowing: each pull request grafts its commit onto the base branch in isolation. A commit that depends on an earlier one may not apply alone. Those are reported in the job summary and skipped rather than forced, and strategy: single will take them in order.

Report drift without opening anything

      - uses: achedon12/regraft@v1
        id: drift
        with:
          create-pull-request: false

      - name: Fail the build when we fall too far behind
        if: fromJSON(steps.drift.outputs.behind) > 20
        run: exit 1

The commit table lands in the job summary either way.

A drift dashboard across many repositories

One workflow in one place, reporting on a whole fleet:

name: Fleet drift

on:
  schedule: [{ cron: '0 7 * * 1' }]
  workflow_dispatch:

jobs:
  drift:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        repo:
          - acme/service-a
          - acme/service-b
          - acme/service-c
    steps:
      - uses: actions/checkout@v7
        with:
          repository: $
          token: $
          fetch-depth: 0

      - uses: achedon12/regraft@v1
        with:
          create-pull-request: false

Each row reports how far behind that repository is, with the missing commits listed. Swap create-pull-request: false for the default and the same matrix opens a pull request on every repo at once.

Inputs

What to graft

Input Default  
version latest Version of regraft to run (npm semver range). Pin it for reproducibility.
from   Start from this template commit instead of the recorded one.
limit   With strategy: single, graft at most this many commits. Lands a long backlog in reviewable chunks.
working-directory . For a repo that is not at the root.

How pull requests are opened

Input Default  
create-pull-request true false reports drift and stops.
strategy single single or per-commit.
open-pull-requests-limit 5 With per-commit, how many to keep open at once.
branch-prefix regraft Branches are <prefix>/template-sync or <prefix>/<sha>.
base current branch Branch the pull requests target.
title-prefix   Prepended to every title, e.g. chore(template): .
labels template-sync Comma-separated.
reviewers   Comma-separated users or teams.
assignees   Comma-separated users.
draft false Open as drafts.
auto-merge false Squash auto-merge. Warns instead of failing if the repo forbids it.

Identity

Input Default  
token github.token Needs contents: write and pull-requests: write.
git-name / git-email github-actions[bot] Committer of the graft commits. The authors stay whoever wrote them upstream.

Outputs

Output  
behind Commits missing before the run.
applied Commits actually grafted.
pull-requests JSON array of pull request URLs.
pull-request-url The first one, for the common single-PR case.

Things that will bite you

fetch-depth: 0 is not optional. The default checkout is shallow, and regraft cannot compute a range against a history it does not have. The action checks and fails with a clear message rather than doing something odd.

A private template needs a token that can read it. github.token can only see the repository the workflow runs in. Give actions/checkout a PAT or an App token, or set template.url to an authenticated URL.

Workflows pushed by github.token do not trigger other workflows. If your template ships .github/workflows/, the sync PR will not run CI under the default token. Use a PAT or a GitHub App token if you need that.

A schedule on a quiet repository gets disabled. GitHub stops scheduled workflows after 60 days without activity in the repo. workflow_dispatch keeps you a manual button either way.


Back to top

Extracted from the tooling that keeps 68 production sites in sync with one template. MIT licensed.