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):
- Tag a release:
git tag v1.0.3
- Action builds PDF and creates release
- Triggers profile repo update
Profile repo (public):
- Receives dispatch event
- Downloads PDF from private release
- Commits PDF locally
- 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)
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)