Skip to content

Run it in GitHub Actions

Goal: every push to your default branch keeps a release pull request up to date, and merging that pull request tags the commit that actually landed.

This guide satisfies what colophon needs from a pipeline item by item. Where a step rests on GitHub's documentation rather than on a run of colophon against GitHub, it says so.

1. Provide a token that is not GITHUB_TOKEN

GitHub states that "events triggered by the GITHUB_TOKEN will not create a new workflow run", apart from workflow_dispatch and repository_dispatch. A tag colophon pushed with it would never start your release build. Create a fine-grained personal access token or a GitHub App installation token with contents and pull requests read/write on the repository, and store it as a repository secret named COLOPHON_TOKEN. If tags are protected, the token's owner must be allowed to create them.

2. Check out the full history

actions/checkout fetches one commit by default. colophon needs every commit back to the last release tag and the tags themselves, and fails with reading commits: iterating commits: object not found on a shallow checkout. fetch-depth: 0 is defined by the action as "all history for all branches and tags".

3. Add the workflow

# .github/workflows/release.yml
name: release

on:
  push:
    branches: [main]

permissions:
  contents: write
  pull-requests: write

concurrency:
  group: colophon-${{ github.ref }}
  cancel-in-progress: false

env:
  COLOPHON_VERSION: v0.3.0

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ secrets.COLOPHON_TOKEN }}
      - uses: actions/setup-go@v5
        with:
          go-version: stable
      - run: go install gitlab.com/phpboyscout/colophon/cmd/colophon@${COLOPHON_VERSION}
      - run: colophon --ci publish --target main --author-name colophon --author-email [email protected]
        env:
          GITHUB_TOKEN: ${{ secrets.COLOPHON_TOKEN }}

  propose:
    needs: publish
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ secrets.COLOPHON_TOKEN }}
      - uses: actions/setup-go@v5
        with:
          go-version: stable
      - run: go install gitlab.com/phpboyscout/colophon/cmd/colophon@${COLOPHON_VERSION}
      - run: colophon --ci propose --target main --author-name colophon --author-email [email protected]
        env:
          GITHUB_TOKEN: ${{ secrets.COLOPHON_TOKEN }}

What each part is for:

  • Two jobs, propose after publish. After the release pull request merges, the release commit is on main and the tag is not there yet, so a single job running both verbs would re-open a pull request for the release just tagged. propose sees the new tag because its checkout happens after publish pushed. Do not fold them.
  • concurrency without cancellation. Two pushes in quick succession must not run publish at the same time; the second waits.
  • token: on the checkout. The checkout's remote is then authenticated with the same token colophon pushes with. colophon supplies the token itself from GITHUB_TOKEN in the environment, sent as the x-access-token basic-auth user; the checkout token matters only for actions/checkout itself.
  • A pinned colophon version. colophon decides what ships, so @latest would let that decision change without a commit saying so.
  • --ci. Marks the run as unattended.

4. Settings on the repository

  • Allow rebase merging for pull requests, so the release commit lands as itself. colophon resolves the landed commit against main rather than from the pull request, so squash merging also works; merge commits are best left off.
  • Require the status checks you care about before merging. The release pull request will run this workflow's pull_request triggers if you add them; it needs nothing of its own beyond what proves it is what colophon wrote.
  • Protect v* tags if you protect anything, and allow the token's owner to create them.

5. Try the safe half first

propose alone opens and updates a pull request and cuts no tag. Run the workflow with only that job while you compare its proposals against whatever released the project before. colophon plan is the even safer check and runs on your laptop against any full clone.

Releases with assets

A repository whose releases carry binaries publishes with --tag-only, builds on the tag, and creates the release afterwards with colophon release --tag:

# .github/workflows/tag.yml
on:
  push:
    tags: ['v*']
jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      # build and upload with goreleaser here; it must not create the release itself
      - run: colophon --ci release --tag "${GITHUB_REF_NAME}" --target main --assets dist --asset-base "<where goreleaser uploaded>"
        env:
          GITHUB_TOKEN: ${{ secrets.COLOPHON_TOKEN }}

Not yet exercised on GitHub, stated from colophon's forge contract rather than from a run: on GitHub a release is created as a draft and published last, so a job killed between the two leaves a draft to delete by hand (go/forge#20); and GitHub release assets are uploaded bytes, which colophon does not send, so --asset-base links become a footer in the notes rather than attached files. Treat this section as the shape to start from and check the first release by hand. Publish a release with its assets has the GitLab path that is exercised daily.

If it fails

symptom cause
reading commits: iterating commits: object not found shallow checkout; set fetch-depth: 0
the tag exists but no tag workflow ran the tag was pushed with GITHUB_TOKEN; use a PAT or App token
publish opened a second pull request for the version just tagged propose ran before or alongside publish; make it a separate job with needs: publish
a push is rejected on the tag the token's owner cannot create protected tags