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.

Your Multi-Agent Orchestrator Is a Serial Killer: Why Parallel Execution Is the Only Way to Scale (And How to Build It)

Your Multi-Agent Orchestrator Is a Serial Killer: Why Parallel Execution Is the Only Way to Scale (And How to Build It)

Your Multi-Agent Orchestrator Is a Serial Killer: Why Parallel Execution Is the Only Way to Scale (And How… ...

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.

Hire Vietnamese Developers: The Strategic Edge for Scalable Tech Teams

Hire Vietnamese Developers: The Strategic Edge for Scalable Tech Teams

TL;DR: Vietnam is now the top offshoring destination for startups and enterprises. With 500k+ developers, strong English skills,… ...

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.