I Automated 80% of My Open Source Maintenance with GitHub Actions — Here’s the Exact Setup
Let’s be honest. Maintaining a popular open-source project is a second job nobody pays you for.
You love the community. You hate the noise. I’ve maintained a ~5K-star Python library for three years. The code is the fun part. The rest—triaging duplicate issues, merging dependency PRs, cutting releases—is a soul-crushing time sink.
Orchestration vs Choreography: Why Your Multi-Agent System Needs Both (and How to Get It Right)
Orchestration vs Choreography: Why Your Multi-Agent System Needs Both (and How to Get It Right) Let me be… ...
So I automated it. Not with some fancy SaaS tool. Just GitHub Actions, a few YAML files, and some opinionated scripting.
Here’s the exact setup that handles about 80% of my maintenance workload on autopilot. It runs in my sleep. No, literally. My team in Ho Chi Minh City reviews the occasional edge case, but the machine does the heavy lifting.
State Management Is the Silent Killer of Multi-Agent Systems: Here’s How We Fixed It
State Management Is the Silent Killer of Multi-Agent Systems: Here’s How We Fixed It You’ve built a shiny… ...
The Problem: Maintenance Isn’t Code
Most devs think maintaining open source means writing features. It doesn’t.
It’s:
- Dependency updates – Dependabot opens 15 PRs a week. You merge 2.
- Issue triage – “Is this a bug or a feature request? Did you read the docs?”
- Stale issues – 90% of open issues are dead. You just haven’t closed them.
- Releases – Changelogs, version bumps, git tags, PyPI uploads. Tedium incarnate.
- CI/CD – Tests fail because someone’s PR doesn’t match your lint rules.
I was spending 6-8 hours a week on this. That’s a full workday. For free.
I decided to build workflows that handle it. Here’s the exact architecture.
Workflow 1: Automated Dependency Hygiene
Dependabot is great at opening PRs. It’s terrible at *closing* the ones that break your build.
I wrote a custom action that runs after Dependabot opens a PR. It checks if the dependency update is a patch, minor, or major. Then it does this:
- Patch updates: Auto-merge if tests pass. No human needed.
- Minor updates: Auto-merge if test coverage stays above 85%.
- Major updates: Open an issue for human review. Block the PR.
Here’s the core logic in a reusable workflow:
yaml
# .github/workflows/dependency-automerge.yml
name: Auto-merge Dependencies
on:
pull_request_target:
types: [opened, synchronize]
jobs:
auto-merge:
if: github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Determine update type
id: semver
run: |
PR_BODY="${{ github.event.pull_request.body }}"
if echo "$PR_BODY" | grep -q "Bumps.*from.*to"; then
# Extract version change
# This is simplified; real logic uses a Python script
echo "type=minor" >> $GITHUB_OUTPUT
fi
- name: Auto-merge if safe
if: steps.semver.outputs.type != 'major'
run: gh pr merge --auto --squash "${{ github.event.pull_request.html_url }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
The result? My dependency count went from 15 open PRs to 2-3 at any time. Patch updates merge in under 5 minutes.
Workflow 2: Intelligent Issue Triage
Issue triage was the biggest time sink. “My code doesn’t work” is not a bug report.
I built a triage action that runs on every new issue. It uses a simple Python script to classify the issue based on keywords and the issue template structure.
yaml
# .github/workflows/issue-triage.yml
name: Triage Issues
on:
issues:
types: [opened]
jobs:
classify:
runs-on: ubuntu-latest
steps:
- name: Check issue body
id: classify
run: |
BODY="${{ github.event.issue.body }}"
if echo "$BODY" | grep -qi "bug\|error\|crash\|fail"; then
echo "label=bug" >> $GITHUB_OUTPUT
elif echo "$BODY" | grep -qi "feature\|request\|would like\|suggestion"; then
echo "label=enhancement" >> $GITHUB_OUTPUT
elif echo "$BODY" | grep -qi "how do I\|question\|help\|tutorial"; then
echo "label=question" >> $GITHUB_OUTPUT
else
echo "label=needs-triage" >> $GITHUB_OUTPUT
fi
- name: Apply label
run: gh issue edit ${{ github.event.issue.number }} --add-label "${{ steps.classify.outputs.label }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
It’s not perfect. But it catches about 70% of issues correctly. The ones marked `needs-triage` are the ones I actually look at.
But, here’s the killer feature: I added a second step that auto-closes issues that don’t use the issue template. If the body is less than 50 characters or doesn’t match the template structure, it gets a `:closed: incomplete` label and a comment asking for more info.
That alone cut my “garbage issues” by 60%.
Workflow 3: The Stale Bot That Actually Works
GitHub’s built-in stale bot is too aggressive. It closes issues after 60 days of inactivity. That’s fine for a busy repo, but for a smaller project, it kills legitimate discussions.
I built my own. It’s more nuanced:
- No label applied: After 90 days, add `stale` label and comment.
- `stale` label applied: After 14 more days, close.
- `pinned` or `security` labels: Never touch.
- Issues with recent maintainer comments: Reset the clock.
yaml
# .github/workflows/stale.yml
name: Mark stale issues
on:
schedule:
- cron: '0 2 * * *' # Daily at 2 AM
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
stale-issue-message: 'This issue has been inactive for 90 days. Please update if this is still relevant.'
close-issue-message: 'Closing due to inactivity.'
days-before-stale: 90
days-before-close: 14
exempt-issue-labels: 'pinned,security,bug'
stale-issue-label: 'stale'
The result? My open issue count dropped from 47 to 12. All of them are either real bugs or active feature requests. No noise.
Workflow 4: Automated Releases with Changelogs
Releases used to take me 30 minutes. Git tag, update `CHANGELOG.md`, bump `__version__.py`, push to PyPI, create a GitHub release.
Now it’s a single command: `git tag v1.2.3 && git push origin v1.2.3`
The workflow does the rest:
yaml
# .github/workflows/release.yml
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate changelog
id: changelog
run: |
PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [ -z "$PREVIOUS_TAG" ]; then
echo "changelog=Initial release" >> $GITHUB_OUTPUT
else
LOG=$(git log "$PREVIOUS_TAG..HEAD" --pretty=format:"- %s (%h)" --no-merges)
echo "changelog<> $GITHUB_OUTPUT
echo "$LOG" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
fi
- name: Create Release
uses: softprops/action-gh-release@v2
with:
body: ${{ steps.changelog.outputs.changelog }}
generate_release_notes: false
- name: Publish to PyPI
run: |
python -m pip install build twine
python -m build
twine upload dist/*
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
It generates a changelog from commit messages, creates a GitHub release, and pushes to PyPI. All in about 90 seconds.
The Results: 80% Automation, Not 100%
I said 80% earlier. Let me be precise.
Here’s the breakdown over the last 6 months:
| Task | Before (hours/week) | After (hours/week) | Automation % |
|---|---|---|---|
| Dependency PRs | 2.5 | 0.3 | 88% |
| Issue triage | 3.0 | 0.5 | 83% |
| Stale issue cleanup | 0.5 | 0.0 | 100% |
| Releases | 1.0 | 0.1 | 90% |
| CI/CD failures | 1.0 | 0.5 | 50% |
| Total | 8.0 | 1.4 | 82.5% |
The CI/CD failures are the hardest to automate. Sometimes a test flakes. Sometimes a contributor’s PR breaks something. I still need to look at those.
But 1.4 hours a week? That’s manageable. That’s a lunch break.
Why This Matters for Teams (Especially Offshore Teams)
This isn’t just a solo dev hack. If you’re building a remote team, especially one using a platform like ECOA AI where our developers in Can Tho or Ho Chi Minh City work across multiple client repos, automation is non-negotiable.
You can’t have a senior engineer spending 20% of their week on dependency PRs. That’s a waste of $3,000/month.
Instead, let the bots handle the noise. Let your team focus on the code that matters. We use these exact workflows across 12 client repos. Our developers barely touch maintenance tasks. They write features.
The One Thing I Didn’t Automate
You might be wondering: what’s the 20% I didn’t automate?
Human communication.
When a contributor asks a nuanced question about architecture, a bot can’t answer that. When someone reports a security vulnerability, you need a human response. When a PR has a complex merge conflict, you need a developer’s judgment.
Don’t automate empathy. Automate the boring stuff.
Ready to Steal My Setup?
All the workflows above are in my project’s `.github` directory. I’m not gatekeeping them. Fork them, adjust the thresholds, and apply them to your own repos.
Honestly, the hardest part is the first week. You’ll tweak the stale bot timing. You’ll realize the auto-merge logic is too aggressive. That’s fine. Iterate.
But once it’s running? You’ll wonder why you didn’t do this years ago.
I know I did.
—
Frequently Asked Questions
How do I prevent GitHub Actions from auto-merging breaking dependency updates?
Use a strict test suite. In your auto-merge workflow, only merge after all CI checks pass. Also, categorize updates by semver—patch and minor can auto-merge, but block major updates for manual review. The workflow above shows this pattern.
Can I use these workflows for private repositories?
Absolutely. GitHub Actions works identically for private repos. You’ll just need to ensure your secrets (like `PYPI_TOKEN`) are configured in the repo settings. The stale bot and issue triage are even more useful for private projects where you control the contributor base.
What if my project uses a different package manager (npm, Go, Rust)?
The auto-merge logic is language-agnostic. The dependency update detection changes, but the workflow structure stays the same. For npm, you’d parse `package.json` version bumps. For Go, check `go.mod`. The concept is identical.
How do I handle security vulnerability reports with automation?
Don’t automate security reports. Use GitHub’s private vulnerability reporting feature. Set up a separate workflow that only notifies maintainers (via Slack or email) when a security issue is filed. Never auto-merge security-related dependency updates. Always review those manually.
Related reading: Outsourcing Software Development: Why Vietnam Is Winning the Offshoring Race
Related reading: Why Smart CTOs Hire Vietnamese Developers in 2025