I’ve been meaning to contribute to open source for a while. Not just in a vague “I should do that someday” way — but actually doing it. Finding a bug, fixing it, opening a PR, and shipping something real. Today I finally did it. Five times.
Here’s what I worked on, what I fixed, and what I learned along the way.

🧰 The Setup
My home lab runs on Unraid, and my media stack is the usual suspects: Plex, Radarr, Sonarr, Tautulli, and a few syncing tools. These are projects I use every single day — which made them the perfect place to start. I already knew where the rough edges were.
I connected the GitHub CLI (gh) to my account, cloned each repo, and got to work.
1. PlexTraktSync — Watchlist Pagination Bug
The Project
PlexTraktSync is a Python tool that keeps your Plex watch history and watchlist in sync with Trakt.tv. If you’ve watched something in Plex, it marks it on Trakt. If something’s on your Trakt watchlist, it appears in Plex. It’s one of those tools that just lives quietly in the background and makes your media life better.
The Bug
Issue #2452: If you had more than 100 items on your Trakt watchlist, only the first 100 were ever fetched. Worse—on a sync, anything beyond item 100 would get removed from Plex, because as far as PlexTraktSync could see, those items weren’t in your watchlist at all.

The Fix
The underlying pytrakt library makes a single API call to users/{username}/watchlist/movies with no pagination. Trakt’s API returns at most 100 items per page by default.
The fix: a new get_watchlist() function in pytrakt_extensions.py that loops through pages of 1000 items until it gets a partial page, signaling the end of the list. One while loop. No more phantom removals.
def get_watchlist(media_type: str) -> list: page = 1 limit = 1000 results = [] while True: data = trakt.core.api().request("get", f"users/me/watchlist/{media_type}", {"page": page, "limit": limit}) if not data: break # ... build results ... if len(data) < limit: break page += 1 return results
🔗 PR #2469
2. Tautulli—Gotify Notifications Ate My Line Breaks
The Project
Tautulli is a monitoring and analytics tool for Plex. It tracks what you (and your friends) are watching, and sends notifications for just about anything—new media added, streams started, playback paused, you name it. I use it to push alerts to Gotify, a self-hosted notification server.
The Bug
Issue #2702: When you typed a notification message in Tautulli with multiple lines—pressing Enter between them — the Gotify notification arrived as a single squished line. Line one\nLine two became Line one Line two.
The cause: Tautulli sends notifications to Gotify using text/markdown content type (required to display poster images). In Markdown, a single newline \n isn’t a line break — it collapses to a space. You need two trailing spaces before the newline: \n.
The Fix
One line of Python in notifiers.py:
# Before'message': body,# After'message': body.replace('\n', ' \n'),
Poster images still work. Line breaks now render correctly. Zero breaking changes.
🔗 PR #2708
3. Sonarr — The {Season Year} Renaming Token
The Project
Sonarr is an automated TV show downloader and organizer. It watches RSS feeds, grabs new episodes, and files them away in whatever folder structure you define using naming tokens like {Series Title}, {Season}, and {Series Year}.
The Feature
Issue #7667: A user wanted season folders that reflected when that season aired, not just the year the whole series premiered. A show that started in 2010 with a new season in 2024 would have all its season folders labelled (2010) — because {Series Year} is the only year token available.
The ask: a {Season Year} token that resolves to the year of the first episode in that season.

The Fix
Sonarr is a C# backend with a React/TypeScript frontend, so this was a different kind of lift. Changes across four files:
- FileNameBuilder.cs — added
{Season Year}handler inAddSeasonTokens, plumbed an optionalint? seasonYearparameter through the naming pipeline, and added aGetSeasonYear()helper that parses the year from episode air dates - FileNameSampleService.cs — passes a sample year so the UI preview works correctly
- NamingModal.tsx — added the token to the season token picker in settings
- GetSeasonFolderFixture.cs — added test cases for the new token
Now you can use Season {season:00} ({Season Year}) and get folders like Season 01 (2013), Season 02 (2014). If no air date is available, the token gracefully returns Unknown.
🔗 PR #8622
4. WordPress Gutenberg — Cleaning Up CI
The Project
Gutenberg is the block editor that powers WordPress. It’s one of the largest and most active open-source projects in the world, with thousands of contributors and a famously competitive “good first issue” board — most tickets get claimed within hours.
The Fix
Issue #78203: The build-plugin-zip.yml GitHub Actions workflow was defining a custom job_status output to pass the build result to downstream jobs — essentially re-implementing something GitHub Actions already provides natively via needs.<job_id>.result.
This is the kind of thing that seems harmless but can introduce subtle bugs or security issues as the codebase evolves. The fix: remove the custom output, use the native context instead.
# Beforeneeds.build.outputs.job_status == 'success'# Afterneeds.build.result == 'success'
Three lines changed. Identical behavior. No footguns left behind.

5. Radarr — Freebox Download Client Crash
The Project
Radarr is the movie equivalent of Sonarr — it monitors RSS feeds for movies you want, downloads them, and organizes your library. It supports a wide range of download clients, including the Freebox — a French ISP’s router with a built-in download manager.
The Bug
Issue #11388: If your Freebox download queue was completely empty, Radarr would throw an unhandled ArgumentNullException and show a health error. The cause: when the queue is empty, the Freebox API returns { "success": true } with no result field. Radarr’s GetTasks() method passed that null straight to callers, which then called .Where() on it and exploded.

The Fix
One null-coalescing operator in FreeboxDownloadProxy.cs:
// Beforereturn ProcessRequest<List<FreeboxDownloadTask>>(request, settings).Result;// Afterreturn ProcessRequest<List<FreeboxDownloadTask>>(request, settings).Result ?? new List<FreeboxDownloadTask>();
Empty queue? Return an empty list. No crash. No health warning. No drama.
What I Learned
- Start with what you already use. Every one of these bugs annoyed a real user — in some cases, me. That context makes it much easier to understand the bug and write a useful PR description.
- Read the issue comments before diving in. Two of the issues I initially looked at already had open PRs from other contributors. Always check before cloning.
- The
ghCLI makes this dramatically easier. Cloning, forking, and opening PRs all from the terminal without touching a browser is a game changer. - Bug reporters often do half the work for you. Both the Tautulli and Radarr reporters pinpointed the exact file, line, and fix. The job was just translating their analysis into code.
- Commit messages matter. A good commit message explains why, not just what. The diff already shows what changed.

All the PRs
- 🐍 PlexTraktSync — PR #2469 — Fix watchlist pagination beyond 100 items
- 🐍 Tautulli — PR #2708 — Fix Gotify notification line breaks in Markdown mode
- ⚙️ Sonarr — PR #8622 — Add {Season Year} renaming token
- 🟦 WordPress Gutenberg — PR #78319 — Replace custom CI job status output with native GitHub Actions context
- ⚙️ Radarr — PR #11464 — Fix Freebox download client crash on empty queue





Leave a Reply