Two years ago I bookmarked Building a self-updating GitHub README. Finally crossed it off my todo list this week.

The Journey

Four months ago, my employer got acquired. Anyone who’s been through an acquisition knows the drill - uncertainty about the future naturally leads to resume polishing.

I got tired of installing TeX distributions just to build my resume. I created a GitHub Action that built the PDF as an artifact. Good enough… I was manually downloading it in case I had to start sending it around.

This week, I decided to tackle the profile automation. I realized those artifacts expire after 90 days (by default) and I wouldn’t be able to link to expiring artifacts. Time to evolve the system.

The Private Repo Problem

I upgraded to creating proper releases. But my resume repo is private. The automation worked great… for me. I was authenticated, so the PDF links worked. Everyone else got 404s.

Could I just make the resume repo public? Sure. But it needs cleanup first, and more importantly, I wanted to learn how to handle this when making a repo public isn’t an option.

I discovered repository_dispatch which lets one GitHub repo trigger workflows in another using a Personal Access Token.

Why the Hard Way?

This cross-repo integration pattern is valuable beyond my use case. What about:

  • Client codebases that must stay private
  • Internal tools with public documentation
  • Any other scenario where “just make it public” isn’t possible

Learning repository_dispatch now means I have this tool ready when I actually need it.

How It Works

Resume repo (private):

  1. Tag a release: git tag v1.0.3
  2. Action builds PDF and creates release
  3. Triggers profile repo update

Profile repo (public):

  1. Receives dispatch event
  2. Downloads PDF from private release
  3. Commits PDF locally
  4. Updates README

The Technical Details

In the resume workflow, after creating the release:

- name: Trigger Profile Repository Update
    if: startsWith(github.ref, 'refs/tags/v')
    run: |
      curl -X POST \
        -H "Authorization: token ${{ secrets.PAT_TOKEN }}" \
        -H "Accept: application/vnd.github.v3+json" \
        https://api.github.com/repos/fz42net/fz42net/dispatches \
        -d '{"event_type":"resume_updated","client_payload":{"version":"${{ steps.get_version.outputs.version }}"}}'

The profile workflow listens for this:

on:
  repository_dispatch:
    types: [resume_updated]

Then it fetches the PDF from the private repo:

- name: Download Latest Resume PDF
    if: github.event_name == 'repository_dispatch' || github.event_name == 'workflow_dispatch'
    run: |
      # Get the latest release from the private resume repo
      echo "Fetching latest release info..."
      LATEST_RELEASE=$(curl -s -H "Authorization: token ${{ secrets.PAT_TOKEN }}" \
        https://api.github.com/repos/fz42net/resume/releases/latest)

      # Check if we got a valid response
      if echo "$LATEST_RELEASE" | jq -e '.assets' > /dev/null; then
        # Extract the download URL for resume.pdf
        DOWNLOAD_URL=$(echo "$LATEST_RELEASE" | jq -r '.assets[] | select(.name=="resume.pdf") | .url')
        
        if [ "$DOWNLOAD_URL" != "null" ] && [ -n "$DOWNLOAD_URL" ]; then
          # Download the PDF
          curl -L -H "Authorization: token ${{ secrets.PAT_TOKEN }}" \
            -H "Accept: application/octet-stream" \
            "$DOWNLOAD_URL" -o resume.pdf
          
          echo "Downloaded resume.pdf from version: ${{ github.event.client_payload.version || 'manual trigger' }}"
        else
          echo "Error: Could not find resume.pdf in release assets"
          exit 1
        fi
      else
        echo "Error: Invalid API response or no releases found"
        echo "$LATEST_RELEASE"
        exit 1
      fi
    env:
      PAT_TOKEN: ${{ secrets.PAT_TOKEN }}

The Result

My profile now shows:

  • Latest blog posts from fz42.net
  • Posts from my employer’s blog
  • Link to current resume (from public repo)

GitHub profile

All updated automatically. The resume source stays private until I’m ready to clean it up.

Full workflows: profile updater (resume builder stays private for now)