Open-source project
ad-m/github-push-action avatar
ad-m/github-push-action

ad-m/github-push-action is the push step every self-modifying workflow rewrites

GitHub actions to push back to repository eg. updated code

1,278 stars239 forksShellMIT

At a glance

What is it?
This GitHub Action exists to solve one problem: a job that commits generated files back to a branch, which needs a token, a ref and a force policy set correctly. The README documents several authentication paths and warns that force-with-lease cannot cross repositories, and its own examples pin the action to `@master` rather than a release tag.
Who is it for?
Reach for this action when a workflow writes files and you want the push step handled for you rather than hand-rolled with git in a shell script, and when you need one of the token paths it covers. Do not adopt it if your only case is a single push to the same branch, where three lines of git in a run step are easier to audit than a third-party action referenced by branch.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 152 days ago.
What is it written in?
Mainly Shell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem is four lines of git, repeated badly

The use cases are listed at the top and they are all the same problem in different clothes: update code placed in the repository by running something over it, track script results by using Git as an archive, publish a page through GitHub Pages, or mirror changes into a separate repository.

What all four share is the push step. A job that writes files has to commit them with a configured identity, decide which ref to push to, and supply credentials, and every one of those three decisions has a failure mode that produces a confusing error rather than an obvious one. This action takes that step as an input, so the workflow declares a token and a branch instead of embedding shell.

The repository is correspondingly small. The top-level entries are `action.yml`, which is the action metadata, `start.sh` and `start.js` as the entry points, a `docs/` directory holding the settings screenshot and supporting material, the README and the LICENSE file. The primary language is Shell.

That smallness is the honest characterisation of the project. It is a wrapper, not a framework, and it earns its place by handling the credential and ref plumbing that every team otherwise gets wrong in a slightly different way.

Every example in the README references @master

This is the detail a careful adopter should notice before anything else, because it appears in every usage example.

Each of the five workflow snippets in the README invokes the action the same way:

yaml
    - name: Push changes
      uses: ad-m/github-push-action@master
      with:
        token: ${{ secrets.GITHUB_TOKEN }}
        branch: ${{ github.ref }}

Meanwhile the project publishes versioned tags: v1.3.0, v1.2.0 and v1.1.0, with v1.3.0 and v1.2.0 both from late May 2026. The default branch of the repository is `master`, which is what the examples reference.

So the documented usage is to consume an action by branch name rather than by an immutable tag. That is a real operational property, not a documentation nit: your workflow will pick up whatever is on that branch at run time, with no commit in your repository recording the change. For an action that holds a write token, that is the difference between an auditable dependency and one you have to trust ongoing.

If you pin to `v1.3.0` instead, you avoid automatic changes to a moving branch, and the cost is that you now own the upgrade. Both are defensible; the point is that the README's examples point at the first, so choose deliberately.

The action also supports a `github_token` input as an alternative spelling to `token`, which the README shows as an equivalent form rather than explaining as an alias.

Two checkout options that decide whether the push works at all

Two `with` values on `actions/checkout` appear in the examples with inline comments explaining why, and both are load-bearing.

The first is `persist-credentials: false`. The comment explains that otherwise the token used is the `GITHUB_TOKEN` rather than your personal access token, which means the credentials git has stored come from the checkout step rather than from whatever token you passed to the push action. Setting it to false forces the push to use the token you supplied.

The second is `fetch-depth: 0`, with the comment that otherwise there would be errors pushing refs to the destination repository. A shallow clone has no history and no remote refs beyond the fetched one, so a push that involves anything other than a fast-forward of the current head will fail. If your workflow commits onto a pull request branch and pushes it, a depth-1 checkout is the most common cause of a rejection that looks like a permissions problem.

Both values appear in the pull request example alongside `ref: ${{ github.head_ref }}`, which is the standard way to get the source branch of a pull request rather than the merge ref the workflow runs against.

The commit step is also spelled out, and the identity matters because a push from a workflow needs an author:

yaml
    - name: Commit files
      run: |
        git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
        git config --local user.name "github-actions[bot]"
        git commit -a -m "Add changes"

Note that the action does not create the commit. It pushes what is already committed, which is why every example has a commit step before it and why the push step can be reused across completely different change-generation logic.

Four credential paths, and the one case that loses force-with-lease

The README documents several ways to authorise the push, and the differences matter more than the syntax suggests.

The simplest is the built-in token, passed as `token: ${{ secrets.GITHUB_TOKEN }}` or `github_token:`, pushing to `branch: ${{ github.ref }}` so the commit lands back on the branch the workflow is running against. For that to work the repository settings have to allow it, which the README covers in seven navigation steps ending in the `Workflow permissions` section, where `Read and write permissions` is selected.

A GitHub App token is the second path. The example combines `tibdex/github-app-token@v1` with `APP_ID`, `INSTALLATION_ID` and `APP_PRIVATE_KEY` secrets, generates a token in a step with an `id: generate_token`, then passes it as `github_token: ${{ env.TOKEN }}`. That pattern is the route for a workflow that pushes to a repository the default token cannot reach.

The third is a cross-repository push, and the README attaches a warning to it: the force-with-lease flag is not possible in that case. The example passes `repository: Test/test` alongside `github_token: ${{ secrets.PAT_TOKEN }}` and `force: true`. So the safe force option is unavailable exactly when you are pushing across repositories, and you fall back to an unconditional force push. That is a genuine risk in a mirroring workflow, where the destination may have commits the source does not know about.

The fourth documented behaviour is overwriting an existing tag, with a snippet that begins a workflow for it.

Permissions can be scoped per job or set globally. The job-level form is what you want when only one job writes:

yaml
jobs:
  job1:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    steps:
      - uses: actions/checkout@v4

The global form places `permissions` at the root of the workflow and applies to every job, which is convenient and broader than most workflows need. The README's own note on the settings page is the right warning to carry: granting `Read and write permissions` lets workflows modify the repository, so only enable it for workflows you trust.

Where the decision is between this and a git push in a shell step

The honest comparison is with the thing you would write instead, because it is short.

A same-repository push is roughly a remote URL, a refspec and a token, which is three `git` invocations in a `run` block. There is no dependency, no third-party code running with your write token, and nothing to audit beyond your own workflow. If that is all you need, it is the better engineering choice and this action is not an improvement.

The action earns its place in three situations. The first is the cross-repository case, where the remote, the token and the ref all change and the plumbing is fiddly enough that getting it right once is worth reusing. The second is the force-with-lease policy, which is a genuine safety property you would otherwise have to remember to pass. The third is tag overwriting, which is unusual enough that having it handled is worth more than the token cost.

Against the alternatives in the same space, the comparison is mostly about trust and maintenance. A maintained community action gives you reviewed code and updates. Doing it yourself gives you zero dependencies and total control. A `git` invocation inline is not cleverer than either, and for a simple push it is exactly as good.

What this action is not is a general commit tool. It does not generate commits, does not compute diffs, does not decide what changed, and has no notion of when a push is desirable. A workflow that runs this on every push without a change detection step will loop if the action or any earlier step also triggers on push, and the README does not address loop prevention, so that guard is yours to write.

Release cadence, licence and the migration state

The repository is not archived, and the last push was on 2026-05-24, about five months before today.

The release history is short and recent: v1.3.0 on 2026-05-24, v1.2.0 on 2026-05-23 and v1.1.0 on 2026-04-10. Three releases across roughly six weeks, all on a 1.x line, with two of them a day apart. That pattern suggests a project that reached a usable state recently rather than one that has been stable for years, and it is worth matching your pinning strategy to: a project at 1.1 to 1.3 is likely to keep changing.

Licensing is MIT, with the LICENSE file at the repository root. That permits use inside a closed-source workflow and inside a commercial product, with the notice retained, which covers the normal case for an action of this size.

On what to check before you depend on it, three items are concrete. First, read `action.yml` in the repository rather than inferring the inputs from the README examples, since the examples only show `token`, `github_token`, `branch`, `force`, `force_with_lease`, `repository` and the checkout interaction, and an action's input list is the contract. Second, decide the pinning policy deliberately, as described above, and record it in your workflow file so a reviewer can see it. Third, if you use the cross-repository path, remember that force-with-lease is unavailable there, and design the workflow so an unexpected divergent history in the destination cannot be silently overwritten.

The example for overwriting an existing tag is truncated in the README as given, so the exact inputs for that case are worth reading in the repository rather than assuming they match the force examples.

Editorial conclusion

Reach for this action when a workflow writes files and you want the push step handled for you rather than hand-rolled with git in a shell script, and when you need one of the token paths it covers. Do not adopt it if your only case is a single push to the same branch, where three lines of git in a run step are easier to audit than a third-party action referenced by branch. Verify first which ref you reference the action by, since every example in the README uses `@master` while the project publishes versioned tags up to v1.3.0, and a branch reference means your workflow's behaviour changes without your commit changing.

Frequently asked questions

What does ad-m/github-push-action do?

It is a GitHub Action that pushes commits a workflow has already created back to a repository using an authorised token. The listed use cases include updating code a linter produced, tracking script results with Git as an archive, publishing through GitHub Pages and mirroring changes to another repository.

Why does my push fail with errors pushing refs?

The README attributes that error to the checkout configuration and shows `fetch-depth: 0` in every example, because a shallow clone cannot push anything beyond the fetched head. The other checkout option that matters is `persist-credentials: false`, which stops git from using the checkout step's token instead of the one you passed to the action.

Can I use force-with-lease when pushing to another repository?

No. The README explicitly warns that the force-with-lease flag is not possible when pushing to a separate repository, and the cross-repository example uses `force: true` instead.

What permissions does the workflow need for this action?

The repository's Workflow permissions setting must be `Read and write permissions`, which the README walks through in the Settings, Actions, General path. More narrowly, you can scope `permissions` at job level with `contents: write` and `pull-requests: write` rather than granting write access to every job in the workflow.

Does the action create the commit for me?

No. It pushes what is already committed, which is why every README example has a commit step before the push step. That step configures a local git identity, in the examples as github-actions[bot] with the corresponding bot email address.

Should I reference the action by branch or by tag?

Every example in the README uses `ad-m/github-push-action@master`, a moving branch reference, while the project publishes tags up to v1.3.0. A tag avoids changes through branch movement, but tags can still be moved; pinning to one means owning the upgrade.

Official sources

  1. ad-m/github-push-action on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/ad-m-github-push-action.svg)](https://hysenlabs.com/projects/ad-m-github-push-action)

Was this useful?