If something requires discipline to stay correct, it will eventually become incorrect.
Why I Automated My GitHub Profile README
Your GitHub profile README is supposed to show people what you are working on right now.
In practice, it shows what you cared about six months ago.
You’re shipping code, writing posts, moving fast. Updating a README is always the lowest priority thing on the list.
I hit that wall. But my profile only changed when I remembered to touch it. The most visible page of my professional identity was always a little bit wrong.
So I stopped treating it like documentation and started treating it like output.
The Real Problem: Manual Updates Don’t Fail Loudly
Nothing breaks when your README goes stale.
There are no tests. No alerts. No CI failures. It just quietly becomes wrong, and nobody tells you.
| Manual README Maintenance | Result |
|---|---|
| Requires human memory | Eventually forgotten |
| No validation | Silent drift |
| Low priority task | Always postponed |
| No feedback loop | Never corrected |
Anything that fails silently will fail indefinitely.
Your GitHub profile is often the first thing a recruiter, collaborator, or fellow engineer looks at. Letting it decay is a credibility tax you pay without noticing.
The Constraint I Set
I wanted my profile README to satisfy three rules:
- Always reflect my latest writing
- Require zero manual intervention
- Fail safely
That led directly to GitHub Actions. Not because they’re flashy — but because they’re boring, predictable, and already trusted.
The Workflow: Treat Content as a Data Source
Instead of manually curating links, I treat my blog as the source of truth and my README as a rendered view of it.
The workflow runs on a schedule, pulls my RSS feed, extracts the most recent posts, and replaces a clearly bounded section of the README. No logic lives in the README itself. It is just output.
Here is the core of the workflow:
name: Update README with Latest Blog Posts
on:
schedule:
- cron: '0 * * * *'
# workflow_dispatch:
jobs:
update-readme:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Fetch Latest Blog Posts
run: |
sudo apt-get update && sudo apt-get install -y jq
pip install yq
curl -s https://www.nickstambaugh.dev/rss.xml | \
xq -r '.rss.channel.item |
map({
title: .title,
link: (.link // .guid | sub("\\s+"; ""; "g")),
date: (.pubDate | strptime("%a, %d %b %Y %H:%M:%S GMT") | mktime)
}) |
sort_by(.date) | reverse[:7] |
map("| " + (.date | strftime("%a, %d %b %Y")) + " | **[" + .title + "](" + .link + ")** |") |
join("\n")' > latest-posts.md
# Extract everything before and after the BLOG-POST-LIST section
sed '/<!-- BLOG-POST-LIST:START -->/q' README.md > README.tmp
echo -e "\n<!-- BLOG-POST-LIST:START -->\n| Date | Title |\n| --- | --- |" >> README.tmp
cat latest-posts.md >> README.tmp
echo -e "\n<!-- BLOG-POST-LIST:END -->" >> README.tmp
tail -n +$(($(grep -n "<!-- BLOG-POST-LIST:END -->" README.md | cut -d: -f1) + 1)) README.md >> README.tmp
mv README.tmp README.md
- name: Commit and Push Changes
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add README.md
git commit -m "Updated latest blog posts" || exit 0
git push
The fetch step converts my RSS feed into a deterministic Markdown table.
Why the README Is Treated as a Template, Not a File
The most important design decision here is what the workflow doesn’t touch.
The workflow only owns a clearly marked section of the README.
<!-- BLOG-POST-LIST:START -->
<!-- BLOG-POST-LIST:END -->
Everything outside those markers is left exactly as-is. The README stays human-editable. One section is machine-controlled.
| Approach | Failure Mode |
|---|---|
| Full README generation | Accidental overwrites |
| Manual edits | Drift |
| Bounded automation | Predictable, safe |
Automation should be surgical, not aggressive. Own exactly what you need to own, and nothing more.
Commit Strategy: Silent When Nothing Changes
git commit -m "Updated latest blog posts" || exit 0
If nothing changed since the last run, the workflow exits cleanly. No failed jobs. No noise. No pointless commit history full of “no changes” entries.
Automation should disappear when it has nothing to do.
This is what keeps teams trusting automation over time. CI spam is annoying. A workflow that stays quiet when it’s not needed is a workflow people trust when it does act.
Comparison: Manual Profile vs Automated Profile
| Dimension | Manual README | Automated README |
|---|---|---|
| Accuracy | Eventually outdated | Continuously current |
| Effort | Ongoing | Zero after setup |
| Visibility | Inconsistent | Predictable |
| Failure mode | Silent | None |
| Trust signal | Weakens over time | Strengthens automatically |
The Broader Lesson
This workflow is about treating representation as a system problem, not a discipline problem.
If correctness depends on someone remembering to do the right thing, it will eventually fail.
Your GitHub profile should be a live reflection of your work, not a historical artifact. The same principle applies to internal tools, dashboards, status pages, and operational runbooks: remove humans from the loop where consistency matters, and let systems handle the rest.
If you want help designing automation that quietly keeps important systems correct, Luniv Technology specializes in exactly that.