I Automated 80% of My Open Source Maintenance with GitHub Actions — Here’s the Exact Setup

1 comment
(GitHub and Open Source) - Maintaining a popular open source repo is a grind. I automated 80% of the busywork—issue triage, stale PRs, dependency updates, and releases—using a custom GitHub Actions pipeline. Here's the exact YAML and the logic behind it.

I Automated 80% of My Open Source Maintenance with GitHub Actions — Here’s the Exact Setup

Let’s be real. Maintaining a popular open source project is a second job you don’t get paid for.

I ran a moderately popular React component library for three years. It had about 4,500 stars, 200+ open issues at peak, and a steady stream of PRs. I loved the community. I hated the *maintenance*. The triage, the stale PRs, the “bump version” requests, the dependency alerts at 2 AM.

Why Outsourcing Software Development Beats Building In-House in 2025

Why Outsourcing Software Development Beats Building In-House in 2025

TL;DR: Outsourcing software to elite offshore teams slashes costs by 40-60%, accelerates time-to-market, and unlocks specialized talent. We’ll… ...

I was burning out. Fast.

So I did what any lazy engineer would do: I automated the hell out of it. I built a GitHub Actions pipeline that now handles roughly 80% of my maintenance workload. It’s not magic. It’s just a few well-crafted workflows that run on cron, on push, and on issue/PR events.

Outsourcing Software: The Strategic Playbook for CTOs in 2025

Outsourcing Software: The Strategic Playbook for CTOs in 2025

TL;DR: Outsourcing software is no longer just about cutting costs. It's about accessing elite talent, accelerating delivery, and… ...

Here’s the exact setup. Steal it.

The Three Pillars of Open Source Automation

You don’t need to automate everything. You need to automate the *grind*. I focused on three areas:

  1. Issue & PR Triage – Labeling, stale detection, and closing abandoned threads.
  2. Dependency Management – Automated updates with sensible grouping and testing.
  3. Release Engineering – Changelog generation, version bumping, and publishing to npm.

Each of these is a separate workflow file in `.github/workflows/`. Let’s break them down.

1. Automated Issue & PR Triage

This is the biggest time sink. Reading every issue, asking for reproduction steps, closing the ones that went cold. I wrote a workflow that runs twice a day.

yaml
# .github/workflows/triage.yml
name: Triage Issues and PRs

on:
  schedule:
    - cron: '0 8,20 * * *'  # Runs at 8 AM and 8 PM UTC daily
  workflow_dispatch: # Allow manual trigger

jobs:
  stale:
    runs-on: ubuntu-latest
    permissions:
      issues: write
      pull-requests: write
    steps:
      - uses: actions/stale@v9
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          stale-issue-message: 'This issue has been inactive for 30 days. Please provide an update or it will be closed in 7 days.'
          stale-pr-message: 'This PR has been inactive for 21 days. Please address review comments or it will be closed in 7 days.'
          close-issue-message: 'Closing due to inactivity. Feel free to reopen if the issue persists.'
          close-pr-message: 'Closing due to inactivity. Please open a new PR with the latest changes.'
          days-before-stale: 30
          days-before-close: 7
          days-before-pr-stale: 21
          days-before-pr-close: 7
          stale-issue-label: 'stale'
          stale-pr-label: 'stale'
          exempt-issue-labels: 'bug,enhancement,help wanted'
          exempt-pr-labels: 'dependencies,automerge'
          operations-per-run: 100

Why this works: It’s aggressive. 30 days of silence on an issue? It gets the stale label. 7 more days? Closed. But I exempt `bug` and `enhancement` labels because those are real work. The `help wanted` label stays open forever—that’s the community’s todo list.

Honestly, the hardest part was convincing myself to close things. But a closed issue is better than a zombie issue that wastes everyone’s time.

2. Dependency Updates That Don’t Break Everything

Dependabot is fine for security patches. But for a library? It creates noise. I switched to Renovate Bot (and wrote about why here). But the real magic is in how I *test* those updates.

yaml
# .github/workflows/dependency-update-test.yml
name: Test Dependency Updates

on:
  pull_request:
    branches: [main]
    paths:
      - 'package.json'
      - 'pnpm-lock.yaml'

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18, 20, 22]
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'pnpm'
      - run: pnpm install --frozen-lockfile
      - run: pnpm lint
      - run: pnpm test -- --coverage
      - run: pnpm build

The key insight: I only run this on PRs that touch `package.json` or the lockfile. That’s it. No point running a full test suite on a documentation PR. This cut my CI costs by about 40%.

But here’s the real pro tip: I use Renovate’s `groupName` config to batch minor and patch updates into a single PR. My `renovate.json` looks like this:

json
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:recommended"],
  "packageRules": [
    {
      "matchUpdateTypes": ["minor", "patch"],
      "groupName": "all non-major dependencies",
      "groupSlug": "all-minor-patch",
      "automerge": true,
      "platformAutomerge": true
    },
    {
      "matchUpdateTypes": ["major"],
      "labels": ["major-update"],
      "automerge": false
    }
  ]
}

Minor and patch updates get auto-merged after the test workflow passes. Major updates? Those get a label and sit in the queue for manual review. I’ve been running this for 8 months. Zero regressions from auto-merged updates.

3. Release Engineering: From PR to npm in 5 Minutes

This was the game changer. Before automation, a release meant:

  1. Manually bumping the version in `package.json`.
  2. Writing a changelog entry.
  3. Creating a GitHub Release.
  4. Running `npm publish`.
  5. Tagging the commit.

That’s 15 minutes of boring, error-prone work. Now it’s a single label.

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

on:
  pull_request:
    types: [closed]
    branches: [main]

jobs:
  release:
    if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release')
    runs-on: ubuntu-latest
    permissions:
      contents: write
      id-token: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
          registry-url: 'https://registry.npmjs.org'
      - run: pnpm install --frozen-lockfile
      - run: pnpm build
      - name: Bump version and generate changelog
        id: version
        uses: mathieudutour/github-tag-action@v6.2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          release_branches: main
          default_bump: patch
      - name: Publish to npm
        run: pnpm publish --provenance --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ steps.version.outputs.new_tag }}
          name: Release ${{ steps.version.outputs.new_tag }}
          body: ${{ steps.version.outputs.changelog }}
          generate_release_notes: true

The workflow is simple:

  1. A PR gets merged that has the `release` label.
  2. The action bumps the version (defaults to patch, but you can use `#major` or `#minor` in the PR title to override).
  3. It generates a changelog from conventional commits.
  4. It publishes to npm with provenance (trust me, this matters for security).
  5. It creates a GitHub Release with the changelog.

I’ve cut my release time from 15 minutes to about 30 seconds. And I never forget to tag the commit anymore.

What I Didn’t Automate (And Why)

Not everything should be automated. I still manually handle:

  • Security vulnerability discussions – These need human judgment.
  • Feature request prioritization – The community needs to feel heard.
  • Code review for complex PRs – AI can help, but I still read every line of a non-trivial PR.

But the boring stuff? The triage, the updates, the releases? That’s all on autopilot now.

The Real Numbers

After 6 months with this setup:

  • Open issues dropped from 200+ to ~45. Most of those are legitimate bugs or feature requests.
  • Average time to first response on issues: 4 hours (down from 3 days).
  • Dependency updates applied within 24 hours of release.
  • Release cycle: 1-2 weeks (down from 4-6 weeks).

I spend about 2 hours per week on maintenance now. Down from 10-15. That’s an 80% reduction.

The Vietnam Connection

You might be wondering why a post about GitHub Actions is on a site about Vietnamese developers. Here’s the thing: I built this pipeline while working with a team in Ho Chi Minh City. We were building a SaaS product, and I was maintaining the open source library on the side.

My Vietnamese colleagues—senior engineers at ECOA AI—showed me a few tricks. Like using `pnpm` instead of `npm` for faster installs in CI. Or the `mathieudutour/github-tag-action` for semantic versioning. They’re not just great coders; they’re pragmatic about automation. They hate repetitive work as much as I do.

If you’re a maintainer burning out, you have two options: hire help (and Vietnam is the best value for that right now) or automate. I did both. The automation came first.

Frequently Asked Questions

Q: Won’t auto-merging dependency updates break my project?

A: Only if you don’t have good test coverage. I run tests on three Node versions (18, 20, 22) before any merge. If your test suite is solid, minor and patch updates are safe to auto-merge. Major updates still need manual review.

Q: How do I handle breaking changes in dependencies?

A: Renovate’s `major` update rules are your friend. I label major updates and review them manually. I also use `npm-check-updates` locally to preview breaking changes before merging.

Q: Can I use this setup for a private repository?

A: Absolutely. GitHub Actions works the same for private repos. Just make sure your `NPM_TOKEN` or other registry tokens are stored as repository secrets. The `stale` action works on private repos too.

Q: What if I don’t use pnpm?

A: Swap `pnpm/action-setup` for the npm or yarn equivalent. The logic is identical. I use pnpm because it’s faster and has better disk usage in CI, but the workflow structure doesn’t change.

Leave a Comment

Your email address will not be published. Required fields are marked *

Ready to Build with AI-Powered Developers?

Hire Vietnamese engineers augmented by ECOA AI Platform + Claude Code. 5x faster, 40% cheaper.