github madeofpendletonwool/PinePods 0.9.1
Version 0.9.1 - Sync Rebuilt, Smarter Refresh, AI Features, and a New Mobile App

3 hours ago

Version 0.9.1 - Sync Rebuilt, Smarter Refresh, AI Features, and a New Mobile App

A big one. 0.9.1 rebuilds podcast sync from the ground up (the gpodder server is now part of the Pinepods API), refreshes every feed on its own schedule, shares one copy of each feed between users, reworks hosts and guests, adds an optional AI container for transcripts and ad skipping, rebuilds the mobile player and look, brings YouTube channels to mobile, lets your devices control each other, and fixes eight security issues. Firewood, the terminal client, gets its 0.2.0 release alongside it (see Firewood 0.2.0). It also carries a long list of fixes from the community: thank you to everyone who sent a PR or a report (see Community contributions at the end).

Note: Items marked beta are new and opt-in. Expect follow-up polish in a later release.


Before you upgrade

  • Back up first. The upgrade merges every user's copy of each episode into one shared set (see Shared feeds and episodes). It can't be undone, and backups taken before the upgrade can't be restored afterwards.
  • Sync apps (AntennaPod etc.) must sign in with an app password. Your Pinepods password no longer works for gpodder apps. Create an app password in Settings → Podcast Sync and enter it in each app once. See The gpodder sync server is now part of the Pinepods API.
  • Port 8042 is gone. If a sync app or reverse proxy pointed at :8042, point it at your Pinepods URL instead.
  • External gpodder / Nextcloud users: if you used "both" before, or Pinepods can't read your saved external password, re-enter the external server in Settings → Sync (a notice tells you if this applies to you).
  • PINEPODS_REFRESH_CRON is deprecated → use PINEPODS_REFRESH_MAX_INTERVAL (default 24h). The old variable is still read, with a warning.
  • The first startup takes longer than usual on big libraries: migrations merge duplicate episodes and podcasts, and every feed is read in full once so podcast details can follow the feed.
  • New optional environment variables: PINEPODS_REFRESH_MAX_INTERVAL, PINEPODS_SYNC_CRON, PINEPODS_SYNC_LOG_RETENTION_DAYS, PINEPODS_PUBLIC_URL, PINEPODS_PODPING_URL, OIDC_DISCOVERY_URL, DB_WAIT_TIMEOUT. All have sensible defaults; see the environment variable page in the docs.
  • Hosted services: host/guest credits use PodPeopleDB v2 and Podping uses the search service. The hosted instances (people.pinepods.online, search.pinepods.online) are updated for 0.9.1. If you self-host the search service, update it to 0.9.1 too.

Security

This release fixes eight security advisories. All affect 0.9.0 and earlier; upgrade as soon as you can.

Advisory Severity What was wrong
GHSA-92ww-5h8f-wc23 High The OIDC login could hand your API key to a site other than your Pinepods server and accept a key it didn't issue; a few endpoints didn't check API keys properly, and API keys could end up in the server log.
GHSA-v8mr-frqx-54mg High A signed-in user could save SQL in their timezone setting and use the playlist page to read any data in the database, including other users' API keys.
GHSA-w54g-63c7-3mpc High The transcript loader would fetch any address a signed-in user gave it and hand back the response, including services on your internal network.
GHSA-jm8m-792v-c66w (CVE-2026-68504) High Any signed-in user could remove another user's podcast, along with its history.
GHSA-83j4-qpw6-7xpr Medium Any signed-in user could read details of another user's podcasts.
GHSA-4rrf-jvw6-vqj9 Medium Any signed-in user could download another user's episodes, including private feeds, with the server supplying that user's feed password.
GHSA-5qh2-4jp2-65cc (CVE-2026-68505) Medium Any signed-in user could add podcasts to another user's account.
GHSA-f9cx-39wv-f4j9 (CVE-2026-68506) Medium Adding a podcast fetched the feed URL without the server's own internal-address guard, so it could be pointed at services on your internal network.
  • If you can't upgrade right away: turn off OIDC login until you do (92ww), and only give accounts to people you trust (the others need a signed-in user).
  • Thank you to @connor-grady (92ww), @de3erve (jm8m, 5qh2, f9cx), @NotAFlightRisk (83j4) and @ttyang57 (v8mr, w54g, 4rrf) for reporting these responsibly.

Other hardening in this release:

  • Ownership checks across the API — a new integration test suite checks that every per-user endpoint refuses other users' episodes, playlists, collections and settings, and fixed the places that didn't.
  • Credentials are redacted from request logs (api_key and others), the stream endpoint no longer logs API keys, and HTTP webhook URLs aren't logged (they often embed a secret).
  • Stored feed passwords are encrypted with a key derived via PBKDF2.
  • A podcast's RSS link is scoped to that podcast — sharing it no longer exposes your other feeds.
  • gpodder apps can't use your account password (app passwords only), so your password and MFA stay out of third-party apps.
  • Gravatar is off by default — turn it on per user in settings if you want it (#985).
  • The unused guest-user code is gone.

Every feed on its own schedule

  • Podcasts are refreshed based on how often they publish: busy shows about every 15 minutes, weekly shows every few hours, quiet ones once a day. A refresh tick runs every minute and picks up the feeds that are due, longest overdue first.
  • Feeds that announce their schedule (podcast:updateFrequency) are checked shortly after each release; feeds that ask to be checked less often (max-age, sy:updatePeriod) are respected, and failing feeds back off.
  • New PINEPODS_REFRESH_MAX_INTERVAL (default 24h) sets the longest gap between refreshes. PINEPODS_REFRESH_CRON is deprecated.

Shared feeds and episodes

  • Users subscribed to the same podcast now share one copy of its episodes. Each feed is fetched and stored once, so adding a show another user already follows is quicker and the database no longer grows with every user. Listening progress, saved, queued and downloaded episodes stay per user.
  • The upgrade merges each user's copies (keeping the furthest listen position). Apps holding old episode IDs keep working: requests with a merged ID are pointed at the new one.
  • Unsubscribing removes only your own state; the feed goes when its last subscriber leaves.
  • A podcast followed by several users uses the most permissive feed cutoff of its subscribers.
  • Sync apps can't create duplicate podcasts any more: a feed known under another address (redirects, new-feed-url, same show GUID) is matched to the existing podcast, and existing duplicates are merged on upgrade.
  • Back up before upgrading — this merge can't be undone, and older backups can't be restored afterwards.

Podcast details follow the feed

  • Podcast name, artwork, author, description and website now update when the feed changes.
  • Details you edited by hand are kept; you can reset them or refresh them from the feed on demand, on web and mobile.
  • Episodes keep their own artwork (itunes:image) instead of the show's.

Hosts and guests, rebuilt

  • Hosts and guests are stored when feeds refresh (Podcasting 2.0 podcast:person tags plus PodPeopleDB), so they show up with the page instead of loading separately.
  • One identity per person across shows — following someone follows them on every show they appear on (#349).
  • Web: hosts and guests on podcast and episode pages, a person page, People You Follow, and you can add people from inside Pinepods.
  • Mobile: a People tab, person page, follow, and add people. Hosts and guests show in the player too.
  • Podcasting 2.0 tag parsing was rewritten, and blank or duplicate podcast categories are hidden.

YouTube channels

  • Much faster, lighter refresh: Pinepods checks each channel's RSS feed and runs yt-dlp only for new videos. A channel several users follow is checked once for all of them.
  • New videos appear live during a manual refresh.
  • Video filters per channel: only add videos longer or shorter than a number of minutes (keep a channel's episodes without its shorts, or skip multi-hour streams), or only videos whose title contains some text.
  • Subscribe on mobile: YouTube is a search source on mobile, with a channel preview, subscribe/unsubscribe and per-channel video settings.
  • Channels with members-only or age-gated videos can be subscribed to (e.g. The Linux Experiment), and the channel description is filled in.
  • yt-dlp updates itself in the container (stable or nightly channel, from the admin settings), so YouTube breakage no longer waits for a Pinepods release (#793).
  • Old videos are no longer removed while they're in your history, queue, saved list, downloads, playlists or collections.
  • Fixed: a YouTube download limit of 0 days now keeps all videos instead of removing them; deleting a YouTube channel no longer fails on Postgres (#879).

Control playback across your devices

  • See what's playing on your other devices — web tabs, phones and desktop apps — and control them: play/pause, seek, skip, speed.
  • "Play here" picks up an episode at the other device's live position and pauses it there; "Send here" hands this device's playback to another one; "Play on ▸ device" in an episode's menu starts it on another device.
  • Works across server replicas through Valkey. If the connection drops, local playback is never affected.
  • Queue: adding an episode puts it right after what's playing (the top of the queue is "play next"), playing an episode moves it to the top, and finishing it removes it. YouTube videos are first-class queue items.

Activity center (web)

  • Notifications are rebuilt as an activity drawer: running tasks, finished tasks (kept until you clear them) and durable messages from the server, such as feed problems, with All / Active / Done tabs.
  • Messages survive a page reload and reach every tab and server replica.

Podcast sync, rebuilt (gpodder / Nextcloud, #636 / #664)

Fixes for the sync problems reported in #636 and #664, followed by a full rebuild of sync (below). If you reported either issue, please retest and let us know.

  • MariaDB/MySQL installs can sync again — the internal gpodder server returned an error for every new device when DB_TYPE=mariadb, so AntennaPod could never finish a first sync.
  • No more sync timeouts on big libraries — uploading subscriptions no longer fetches every feed while AntennaPod waits; Pinepods adds the podcasts (with artwork and episodes) on its next sync.
  • Stuck uploads fixed — a failed upload no longer leaves a database transaction open, which made later syncs hang (the 60-second waits in #636).
  • Offline listening syncs correctly — the time you actually played an episode is kept (it was replaced with the upload time), and plays uploaded late still reach your other devices.
  • Positions no longer reset to 0 in AntennaPod when the play came from a client that only sends a position (e.g. gPodder desktop), or from Pinepods for an episode with no known duration.
  • No lost changes at second boundaries, and unsubscribe-then-resubscribe no longer sends a podcast as both added and removed.
  • Wrong passwords now get one clean 401 instead of a broken response.
  • Pinepods → external gpodder/Nextcloud: a failed upload now fails the sync and retries next time instead of silently dropping your changes; Nextcloud "Force sync" works (it was sending the encrypted password); Nextcloud play actions only update your own episodes and download/delete actions no longer mark episodes played or wipe history; syncs started mid-change no longer skip changes made while they ran.
  • Completion threshold for synced plays follows your auto-complete setting (60 seconds if it's off).
  • MariaDB/MySQL: one listening-history entry per episode — marking an episode played (and some sync updates) added a second history row instead of updating the existing one, so an episode could show a different position or played state depending on the screen. Existing duplicates are merged on upgrade (the most recent one is kept).

The gpodder sync server is now part of the Pinepods API

The separate gpodder service is gone; the same sync API is now served by the main Pinepods server. Apps such as AntennaPod keep using your Pinepods URL and username, but they now sign in with an app password (see below).

  • Action needed: apps sign in with an app password, not your Pinepods password. An app set up with your Pinepods password stops syncing after the upgrade. In Settings → Podcast Sync, generate an app password and enter it in the app instead. Each app needs this once. This keeps your account password (and MFA) out of third-party apps, and it gives accounts that sign in with OIDC a password to use.

  • Port 8042 is gone. It was the old service's internal port. If you pointed a sync app (or a reverse proxy) at :8042 directly instead of at the Pinepods URL, switch it to the Pinepods URL. There's no compatibility listener.

  • Existing sync tokens keep working. The token Pinepods generated for its internal sync is kept as an app password, so anything that used it still logs in.

  • Only users with the PinePods sync server turned on can use it. Users who sync only with an external gpodder server or Nextcloud could previously also log in to the built-in one; they now get a 401 until they turn it on in Settings.

  • Turning the sync server on in Settings no longer replaces an external gpodder server you already sync with; both keep working. Your current listening progress is published right away, so a newly connected app picks it up on its first sync.

  • Changes from your apps reach Pinepods right away: played positions and subscriptions are applied when the app syncs instead of on Pinepods' next scheduled sync, and new podcasts are imported in the background.

Syncing with an external gpodder server or Nextcloud, rebuilt

Syncing with an external gpodder server (gpodder.net, oPodSync, …) or the Nextcloud gpoddersync app now uses one new sync engine.

  • The first sync after upgrading is a full reconcile. Pinepods pulls everything from the server and sends your library and listening progress. It only adds; nothing is unsubscribed on either side. "Force full sync" does the same.
  • Nothing is lost when a sync fails. If the server errors part-way, Pinepods tries again on the next sync from where it stopped. The changes it already sent aren't sent twice.
  • The server's own sync position is used. Changes made on other devices while Pinepods' clock or the server's clock is off are no longer skipped.
  • Your other devices' changes aren't echoed back, and a change you make in Pinepods wins over an older one from the server for the same podcast until Pinepods has sent it.
  • "Both" works: with the PinePods sync server turned on and an external server set up, changes from apps on either side reach the other (e.g. AntennaPod on Pinepods ↔ a phone on Nextcloud).
  • Positions no longer reset to 0 in AntennaPod for plays Pinepods sends. "Mark as played" is sent too. Uploads go in batches, and each sync logs in once instead of once per request.
  • Pinepods syncs as its own device ("PinePods") on gpodder servers. Earlier versions could pick another app's device, which hid Pinepods' changes from that app. If you're affected, Pinepods moves to its own device automatically.
  • If you used "both" before upgrading (or Pinepods couldn't read your saved external password), enter your external server again in Settings → Sync. Saving it clears the notice.
  • Setting up Nextcloud no longer turns off the PinePods sync server, and your current setup is kept until the Nextcloud login completes.
  • Fixed: the Nextcloud login flow wrote the new app password to the server log.
  • Changes reach the external server within minutes. Pinepods sends your plays, "mark as played" and subscriptions shortly after you make them: about 30 seconds after you stop, and at least every 2 minutes while you keep listening. Changes from apps on the PinePods sync server are passed on the same way. On top of that, everything is synced every 5 minutes (PINEPODS_SYNC_CRON, cron format) instead of after each feed refresh.
  • A sync server that keeps failing is retried less often: 30 minutes more per failure in a row, up to once a day, until it works again. "Sync now" and "Force full sync" always run.
  • The refresh button no longer waits for the sync server. With sync turned on, the sync runs alongside the refresh and its result is shown when the refresh finishes.

New sync settings

Settings → Podcast Sync is rebuilt around the two things you can turn on: the PinePods gpodder server, and one external sync server.

  • PinePods gpodder server: one switch, plus the server URL and username to enter in your apps (with a copy button).
  • App passwords: create a separate password for each app (shown once) and revoke it on its own. Revoking one signs out the app that uses it straight away. Apps can only sign in with an app password.
  • Devices: see which apps have synced and when, rename them (the name sticks, even when the app updates its device), or remove one (its history stays; the app gets a new device the next time it syncs). Pinepods' own device is listed but can't be removed. The "default device" picker is gone: Pinepods picks its own device.
  • External sync (gpodder server or Nextcloud): URL, username, password and, for gpodder, the name Pinepods syncs as. "Test connection" checks the login (and lists your devices on a gpodder server) without saving anything; saving checks it again and starts the first sync in the background, shown in the activity panel. The Nextcloud login in the browser still works, or enter a Nextcloud app password directly.
  • Status: when the last sync worked, the last error, and when automatic syncing resumes after repeated failures. "Sync now" and "Force full sync" are next to it.
  • Statistics come from Pinepods' own sync history, so opening them no longer contacts the sync server.
  • The re-enter notice for external servers that couldn't be carried over shows on the home page and in the sync settings, with a link there.
  • The server URL shown for apps is the address you opened Pinepods with. To show a fixed address instead (e.g. when you browse Pinepods on your LAN but apps reach it through a reverse proxy), set PINEPODS_PUBLIC_URL (e.g. https://pods.example.com). The bundled nginx now passes the original host (with its port) and an outer proxy's https through to the API, so Pinepods also sees the address you used.
  • gpodder login cookies are marked Secure behind an HTTPS reverse proxy (they never were, because the bundled nginx replaced the proxy's https with http).
  • User statistics show the actual sync setup (they always said "Internal gpodder" before), including the PinePods server and an external server side by side.
  • Sync history is compacted after 90 days: older changes that a newer one replaced are dropped once every external server has them. The latest state of every podcast and episode is always kept, so a new app still gets everything. Set PINEPODS_SYNC_LOG_RETENTION_DAYS to change the window (0 keeps everything).
  • Podcasts an app subscribes to that can't be added (the feed is gone, or not a feed) are retried for about a week and listed under Settings → Feed problems, where you can try again, unsubscribe everywhere, or dismiss them.
  • For developers: the settings are available as /api/sync/* (see the API docs). The old /api/gpodder/* routes keep working for older clients.

Faster, lighter feed refresh

  • No more duplicate episodes — two refreshes running at once (e.g. the scheduled one and the refresh button) could insert the same new episode twice. Existing duplicates are merged on upgrade, keeping your furthest listen position, saves, queue, downloads and share links, and the database now prevents new ones.
  • Adding a podcast is much faster — the feed is downloaded once instead of twice, the back catalogue is inserted in one go, and missing durations are only looked up for the newest episodes up front (the rest fill in shortly after in the background).
  • Less bandwidth and load on podcast hosts — feeds are downloaded compressed, connections are reused, and a feed whose content hasn't changed is skipped even when the host doesn't support caching headers.
  • Polite to busy hosts — a host that answers "too many requests" or "unavailable" is left alone for as long as it asks (Retry-After), and failing feeds are no longer fetched twice per refresh.
  • The refresh button checks smarter — it now asks the host whether the feed changed (instead of always re-downloading it) and skips feeds the scheduled refresh checked in the last two minutes or is fetching right now.
  • No overlapping refreshes — a scheduled refresh that comes round while the previous one is still running is skipped, and a feed is never fetched by two refreshes at once.
  • Faster restarts — the startup refresh runs in the background once the server is up, and skips feeds refreshed in the last 15 minutes.
  • Less work per refresh — category collections and playlist episode counts are updated only for the new episodes and the users who got them, instead of everything every 30 minutes. Episodes are auto-completed when the position is saved, and a catch-up check runs hourly instead of nightly (covering the last week).
  • Fixed: collections with a category rule could pick up episodes from other users' podcasts in the same category.

Feed problems you can see

Pinepods used to retry a broken feed quietly forever. Now:

  • Settings → Feed problems lists your podcasts whose refresh keeps failing, with what went wrong in plain words (the feed is gone, the host refuses access, it isn't a podcast feed, the host can't be reached or keeps erroring) and when it started. "Refresh now" tries straight away; "Unsubscribe" removes the podcast.
  • One message in the activity panel when a podcast has failed to update for a day (a feed that's gone is reported after three tries), and another when it works again. No repeats in between. The podcast's page shows a note too.
  • Podcasts a sync app subscribed to that couldn't be added are listed in the same place (see above).

Podping: new episodes within a minute (optional)

  • Podping is how many podcast hosts (Buzzsprout, Spreaker, RSS.com and others) announce new episodes as they publish. Turn it on under Settings → Admin → Podcast update services and Pinepods refreshes those feeds within about a minute instead of waiting for the next scheduled refresh.
  • Private by design: Pinepods reads every announcement from the search service and matches them against its own subscriptions locally. What your server follows is never sent anywhere.
  • It's off by default. It uses the search service you already configured (SEARCH_API_URL); point PINEPODS_PODPING_URL at another relay if you like. Self-hosted search services follow Podping themselves (set PODPING_ENABLED=false there to turn that off; HIVE_API_NODES picks the Hive API nodes).
  • The same admin section shows whether the search service answers. When it doesn't, searching now says so instead of failing silently.

AI / ML (optional companion container)

A new, optional AI service (Immich-style — run it only if you want these features):

  • New pinepods-ai container — a separate Python service that does the heavy ML work; the API talks to it over an internal client. Published multi-arch (amd64 + arm64).
  • Episode transcription (#726) — generate per-episode transcripts, viewable on web and in the new mobile player.
  • Ad detection & skip (#790) (beta) — detect ad segments server-side and skip them on playback, on web and mobile.
  • Built for dynamic ads: ads are tracked against the exact audio copy they were detected on, one server copy is shared between users, and ads are only skipped when the copy you're playing matches — with a clear warning when it doesn't. Server downloads used for detection can be deleted when you finish the episode.
  • Recommendations / Discover — a new Discover experience for finding podcasts and hosts.
  • AI settings — admin settings to enable and configure the AI features, with a job queue view and clear errors when a job fails (including running out of memory).
  • Deployment — Kubernetes Helm templates and compose wiring for the AI container (fully optional).

Server-side audio processing

  • Trim silence (#727) — auto-detect and skip silent gaps during playback, configurable per the settings UI.
  • Unified skip segments — silence and ad ranges are applied together on playback, across web and mobile.

Mobile player, rebuilt

  • Faster stream start, pause and seek.
  • New Now Playing screen with chapters, transcript and Up Next sheets.
  • Next-episode prefetch and an Android stream cache; exact ad skipping.
  • Resume where you left off: the app resumes from the furthest of your on-device progress, unsynced offline progress and the server's position, and play-start no longer overwrites newer offline progress. CarPlay playback is now tracked and synced like Android Auto.
  • Playback speed sticks — no more resetting to 1.0x on episode start or after an interruption; a podcast's own speed no longer overwrites your default (#882, #963, #973).
  • iOS resumes after short interruptions (Siri, notification readouts); phone calls still pause.
  • Server-downloaded and YouTube episodes stream properly on iOS — seeking works and playback no longer restarts every minute (#980).
  • "Play from beginning" really starts at 00:00.
  • Android keeps playing after an ad skip — a stop from a car or headset right after the skip no longer ends background playback; a later stop pauses instead of closing the player.
  • The player recovers after load errors, Android follows http→https redirects, sleep timer fixes, and logout works without restarting the app.
  • Play episodes of podcasts you don't follow without errors (#965).

Mobile look and feel

  • New soft UI across home, feed, queue, playlists, search and stats, with Phosphor icons and IBM Plex Sans (same font as web).
  • Redesigned podcast and episode pages — compact action strip, no loading flash.
  • Persistent mini player that no longer resets between pages; the nav bar slides away on pushed pages.
  • Episode cards: one consistent card everywhere, play button styles and artwork-derived colours (new settings), live progress on the Home now-playing card.
  • Logo placeholders for podcasts and episodes without artwork (#984) and an Android themed icon (#977).
  • Snappier lists — the queue and feed read local data once per list and do heavy work off the UI thread; HTTP connections are reused with timeouts.
  • Home refreshes after queue changes or playback starts; episode buttons no longer need several taps; sort and filter stay put after list actions on a podcast page.
  • Subscribe links and "Add RSS" open the Pinepods podcast page; leftover screens from the Anytime fork are gone, and CarPlay's queue is the server queue.

Mobile download management (#626)

  • Wi-Fi-only downloads — gate downloads to Wi-Fi to avoid burning mobile data.
  • Prefer server copies / two-way mirror — when the server already has an episode downloaded, mirror that copy via the stream endpoint instead of re-fetching from the original feed.
  • Automatic queue downloads — episodes added to the queue can download automatically.
  • Automatic cleanup — delete device downloads when finished or after N days, with global defaults and per-podcast overrides.
  • Server download settings on mobile.
  • Fixed: downloads over 2 GiB (#1037), signed CDN download links (#1035), SD-card downloads on Android 13+ (#983), duplicate downloads of the same episode (#960), deleting a download now removes the file, and an episode whose file went missing falls back to streaming and stops showing as downloaded.

Download metadata enrichment (#451 / #533 / #658)

  • ID3 tag enrichment — downloaded files are enriched with proper metadata.
  • Sidecar cover art & metadata + folder.jpg — optional, admin-controlled.
  • Auto-delete — configurable automatic cleanup of old downloaded episodes.
  • Removing a podcast removes its downloaded files (#1038), and bulk-deleting downloads removes the files too (#970).

Queue overhaul (mobile + web)

  • Server-backed auto-advance — when an episode finishes, playback advances through a clear priority chain: playlist continuation → per-podcast auto-play-next → server queue, with stale/completed entries cleaned up first. Auto-play next skips episodes you've already finished (#956).
  • Finished episodes are marked finished on the server by the mobile app (the 0.9.0 app only marked them on the phone, so they showed 100% but not finished), including when the end-of-episode sleep timer stops playback.
  • Episode duration correction — feeds that ship a missing/zero duration are corrected to the real decoded length on play, preventing bad rows that could break the web player.
  • Queue management modal (web) — manage the up-next queue directly, and Queue is in the web sidebar.
  • The web queue panel updates right away after adding or removing an episode (#873).

Android Auto / CarPlay (#868 / #874)

  • Fixed forward/back seek behavior on head units.
  • Added dedicated rewind / fast-forward car icons and hardened the native player integration.

Mobile custom TLS certificates (#800)

  • Import a server CA and/or mTLS client certificates for self-hosted setups behind custom PKI.
  • Honest login errors — clearer messaging when a TLS/auth handshake fails.

Favorites & mobile UX polish

  • Podcast-level favorites — mark podcasts as favorites and filter feed / saved / history to favorites only.
  • Clickable podcast names — jump straight to a podcast from anywhere it's shown.
  • Card polish — loading spinner on the play button, larger play target, and other episode-card papercuts fixed.
  • Collection picker fix (mobile + web).

OIDC and login

  • PKCE (S256) is sent on web OIDC logins, so providers that require it work (#928).
  • Custom ports work — the token exchange reuses the exact redirect URI (#993, #689).
  • OIDC discovery: set OIDC_DISCOVERY_URL, or use the new Discover button in provider settings, instead of entering every endpoint (#1017).
  • Username claim fallback restored: without a configured claim, Pinepods uses preferred_username, then the email (#943).
  • Empty OIDC environment variables count as unset, empty provider icons are hidden (#991), and the "Or continue with" divider is hidden when standard login is turned off (#990).
  • Desktop app login fixes, including logins with a second factor.

Notifications

  • HTTP webhook notifications now fire for new episodes (before, only the test button worked), with POST JSON or GET, and an optional bearer token. Telegram chat IDs are parsed correctly.

Web fixes

  • Paged episode lists sort and filter the whole podcast, not just the loaded page (#954), and paging never repeats or skips episodes that share a date, length or title.
  • Subscribing from an episode preview keeps the Podcast Index ID, so those podcasts no longer need manual matching (#886); Subscribe on the episode page works for feeds without categories.
  • The play/pause button on episode rows stays in sync with playback (#449).
  • Playlists tolerate missing preview fields (#957); podcast sort and shared episode links fixed; empty searches aren't sent; labels and dialog titles fixed; the Podcast Index notice no longer claims it's required for hosts.
  • 20 missing English translation strings added (#889).

Server and database fixes

  • MariaDB/MySQL playlists: viewing a playlist no longer fails with a BLOB decode error, its podcast filter is applied (#773), and the playlist episode count badge matches the list.
  • Waits for the database at startup for up to 5 minutes by default (DB_WAIT_TIMEOUT, 0 = forever), with a clear error if it never comes up.
  • Clear error when Valkey/Redis is unreachable at startup (#975).
  • RSS feeds declare UTF-8 (#992); setting SERVER_URL is honored by the container instead of being overwritten by the hostname (#903).
  • Truncated API responses fixed — nginx's temp directories get the right owner when the container runs as root, which could cut large responses short and show empty episode lists on mobile (#940).
  • Audio and video responses skip gzip; deleting users and removing admins fixed; new users get stats rows; history positions and collection lists fixed on MySQL.
  • Playback start makes one database trip instead of two for its settings.

Localization

  • Web translations caught up with Weblate: Danish, French and Spanish updates plus translators' pending suggestions. Untranslated strings fall back to English.
  • Flutter localization scaffolding added for the mobile app (l10n).

Infrastructure & deployment

  • The separate Go gpodder service is gone — one less process in the container.
  • build-all.sh build helper; updated docker-compose files for PostgreSQL / MySQL / MariaDB.
  • Kubernetes Helm chart additions (the optional AI service and an optional local-media volume) and AWS Terraform updates.
  • Multi-arch publish workflow for the AI image.
  • Dependencies updated across the stack (Rust, web, desktop, mobile, Python 3.14, search service on Debian trixie); CI actions on current majors.
  • Tests: a new rust-api integration suite runs on PostgreSQL and MariaDB in CI (including a gpodder protocol conformance suite and migration tests), plus a PR-gating mobile test suite.

Firewood 0.2.0 (terminal client)

Firewood, the terminal client for Pinepods, gets its first release since 0.1.0 alongside 0.9.1. It's rebuilt from the ground up:

  • A new terminal UI with a sidebar, every page one key away (Home, Feed, Podcasts, Queue, Saved and collections, Playlists, History, Downloads, Search, Devices, Now Playing, Settings), a command palette (: or Ctrl+P), mouse support, rebindable keys, and the same 25 themes as the web app.
  • Streaming playback that starts in a second or two, with seeking, 0.5× to 3× speed, reconnects after network drops, and progress saved to the server as you listen.
  • Skips ads and silence Pinepods has detected, plus each podcast's intro and outro skip times.
  • Queue and playlists: the next episode in your queue (or playlist) plays when one ends.
  • Chapters, transcripts and cover art on Now Playing (real images in kitty, sixel and iTerm2 terminals).
  • Remote control: Firewood shows up as a device in the web and mobile apps' device lists, so you can beam episodes to it and control it, and it can control your other devices.
  • Headless mode: pinepods_firewood --headless runs the player and remote control without the UI, so a Raspberry Pi or spare machine can sit there as a speaker your other apps play on.
  • Media keys and the system's now-playing controls on macOS and Linux (MPRIS); the login is kept in the OS keychain.
  • Builds for Linux (amd64, arm64), macOS (Apple Silicon and Intel) and Windows, plus a .deb.

Remote control needs a 0.9.1 server.


Community contributions

Thank you to everyone who contributed code to this release:

  • @conorbronsdon — MariaDB/MySQL playlist fixes (#913, #917, #921), YouTube channel delete fix (#918), SERVER_URL fix (#914), Podcast Index ID on subscribe (#901), missing translation strings (#900), nginx temp-dir ownership fix for truncated responses (#952), OIDC username-claim fallback (#953), Valkey startup docs (#981)
  • @dmfrey — mobile: missing-download fallback and badge repair (#909, #910), live progress and Home refresh (#911, #912), re-entrant tap guard (#896), HTTP connection reuse and timeouts (#897), unit test coverage (#919)
  • @ashtonian — iOS playback speed and interruption handling (#1011), resume and seeking for server downloads (#1012), replay from the beginning (#1008), fewer database trips on playback start (#1009)
  • @mrmichaeladavis — playback speed resetting to 1.0x, iOS interruption resume (#974)
  • @honzup — Range requests for streamed episodes (#986), play/pause desync on episode rows (#948)
  • @KallanX — HTTP notifications for new episodes (#1034)
  • @stevenolen — PKCE for web OIDC (#944)
  • @connor-grady — episode sort and filter kept after list actions on mobile (#941)
  • @cristianchiriac — OIDC divider hidden when standard login is off (#994)

And to those whose PRs and diagnoses the fixes in this release were built on: @xoiram (episode paging, #972), @iSaluki (database startup wait, #949), @gab9281 (members-only YouTube channels, #958), @numbawon (subscribe without categories, #979), @davegallant (the #980 streaming diagnosis), and everyone who filed issues and tested fixes.

I'm very sorry if I missed anyone who contributed. If I didn't put your name in here please get it touch and I'll correct it.

Security reports: @connor-grady, @de3erve, @NotAFlightRisk, @ttyang57.

Android and IOS app updates will appear in their stores in the coming couple of days.

Don't miss a new PinePods release

NewReleases is sending notifications on new releases.