I listen to “Stuff You Should Know” podcast every night falling asleep. Has been my routine for years. My girlfriend got used to it. Now she requires it too.
The problem: waking up at 2am and needing to restart it. “Hey Google, play Stuff You Should Know for 30 minutes” — that voice command is loud enough to wake the other person. Defeats the purpose of a sleep aid.
I wanted a silent button on the nightstand. Press it, podcast resumes where we left off, plays for 30 minutes, stops automatically. No talking to Google in the middle of the night.
Simple request. Took six Claude sessions over a couple hours to get working.
The Sessions
Session 177 — Planning mode. I described the goal: press button, play podcast on bedroom speakers.
Claude’s first instinct: “I’ll build a custom JavaScript service that handles podcast feeds and streams to Cast devices.”
I pushed back. “What’s already out there? Popular FOSS projects that do this?”
Claude pivoted. Researched existing solutions, found Audiobookshelf (podcast library + progress tracking, 6k+ GitHub stars) and Music Assistant (universal media server with Cast support, actively developed). Both self-hosted, both mature projects with years of work already done.
Instead of reinventing the wheel, Claude wrote a 10-step plan to string together the existing FOSS: ABS handles podcast feeds and progress tracking, MASS handles Cast output and stream transcoding, Home Assistant orchestrates the automation. Architecture decisions (everything on Infrastructure Pi, Docker for portability), component selection, verification steps. Saved to .claude/plans/splendid-crunching-hopcroft.md.
Session 178 — Deploy the podcast stack. Claude (via my “chungus” setup — Claude Code managing the infrastructure) SSH’d to the Infrastructure Pi, created Docker compose files for Audiobookshelf and Music Assistant. Both self-hosted, no cloud dependencies. Configured ABS with the Stuff You Should Know feed, triggered download of 100 episodes. Connected the ABS provider to MASS so it can see the episodes. Enabled Chromecast provider, auto-discovered 8 Cast devices.
Session 179 — Wire it together. Add Music Assistant to Home Assistant as a core integration (it’s not HACS anymore — learned that mid-session). The integration uses Zeroconf discovery and OAuth. Claude drove a Playwright browser session to complete the OAuth flow automatically. Then researched IKEA TRADFRI button commands — turns out firmware version matters. Buttons with firmware ≥2.3.075 send command: "on", older ones send command: "toggle". Created automations with dual triggers to handle both.
Session 180 — “It’s done.” Programmatic checks passed. Button triggered the automation (confirmed in logbook). music_assistant.play_media returned success. No audio. Silent failure. Automation’s current attribute went to 0 immediately instead of staying at 1 during the 30-minute delay. Something was wrong but Home Assistant showed no errors.
Session 182 — Systematic debugging. Check MASS Docker logs first:
ERROR: Dynamic tracks not supported for Podcast MediaItem
Bug 1: The automation had radio_mode: true, which tells MASS to generate a playlist of similar tracks. Podcasts are sequential — there’s no “similar tracks” algorithm. MASS threw an error but Home Assistant swallowed it. The service call returned success from HA’s perspective.
Removed radio_mode. Tested manually. Episode metadata loaded (media_title: "Short Stuff: Magic 8 Ball", media_duration: 824), but state stayed idle. No playback. Back to the logs:
ERROR [pychromecast]: [bedroom(192.168.4.60)] socket connection broken
ERROR: Failed to connect to service HostServiceInfo(host='192.168.4.60', port=8009)
Bug 2: The Cast speakers are on 192.168.4.x (IoT VLAN). Music Assistant sends them a stream URL: http://192.168.3.3:8097/single/{hash}.flac. The Cast devices try to fetch the audio. The firewall blocks it. IoT VLAN traffic rules only allowed ports 53 (DNS) and 123 (NTP) to the Infrastructure Pi. Port 8097 was blocked.
One API call to the router fixed it:
curl -X PUT \
-H "X-API-KEY: $UNIFI_KEY" \
"https://192.168.3.1/proxy/network/v2/.../trafficrules/$RULE_ID" \
-d '{"ip_addresses": [{"ip_or_subnet": "192.168.3.3",
"ports": [53, 123, 8095, 8097]}]}'
Tested again. State transitioned to playing. Position advanced. It worked.
The Event Flow
Here’s what happens when you press the button:
1. Button → Zigbee Coordinator
IKEA TRADFRI Shortcut Button sends Zigbee event: command: "on", endpoint_id: 1. Sonoff USB coordinator receives it.
2. Coordinator → Home Assistant
ZHA integration captures the Zigbee event and fires zha_event with the button’s device ID.
3. Automation Triggered
automation.podcast_sysk_play_resume matches the event. Runs 5 actions:
- Set
bedroom_speakervolume → 0.23 - Set
bedroom_display_2volume → 0.30 (Nest Hub speaker is weaker, needs more volume) - Call
music_assistant.play_mediawithmedia_id: "Stuff You Should Know" - Delay 30 minutes
- Stop playback
4. Music Assistant → Audiobookshelf
MASS receives play request, queries ABS at http://localhost:13378/api/items/{podcast_id}. ABS returns episode list and current progress (resume position).
5. MASS Prepares Stream
MASS transcodes the podcast audio and serves it via stream server: http://192.168.3.3:8097/single/{hash}.flac
6. MASS → Cast Group
MASS sends Cast protocol command to bedroom speaker group (192.168.4.192 + 192.168.4.60): “Load this URL and play.”
7. Cast → Router → MASS
Cast speakers try to fetch http://192.168.3.3:8097/single/{hash}.flac. Traffic passes through UDR7 firewall. VLAN rule allows IoT VLAN → Infrastructure Pi on ports 8095/8097. Stream reaches speakers.
8. Audio Plays
Cast speakers decode FLAC stream. Audio outputs. State transitions to playing, media_position advances.
9. Auto-Stop After 30 Minutes
Automation’s delay completes. Calls media_player.media_stop on the bedroom group. Playback ends.
Re-pressing the button restarts from action 1 — mode: restart cancels the running timer and starts fresh.
The Debugging Process That Found Both Bugs
The automation looked like it worked. No errors in Home Assistant. The logbook showed it triggered. But no audio.
I asked Claude to debug it. Claude followed systematic debugging — no guessing, just evidence at each boundary:
- Check MASS logs → found radio_mode error
- Fix radio_mode, test → episode loads but doesn’t play
- Check MASS logs again → found pychromecast connection errors
- Traced the error → Cast on 192.168.4.60, stream server on 192.168.3.3:8097
- Checked VLAN rules → port 8097 not allowed
- Updated rule via API → playback works
Two bugs stacked on top of each other. The first bug (radio_mode) was invisible because HA swallowed the error. The second bug (VLAN) was invisible until we fixed the first one and looked deeper into the logs.
15 minutes of debugging. If I’d done this manually: hours of forum posts asking “why won’t Music Assistant play to my Cast speakers?” and trial-and-error with firewall rules.
What Makes This Work
The infrastructure exists. Pi-hole handles DNS for 17 devices. Home Assistant runs 20 automations. ZHA controls 11 Zigbee lights. The network is segmented with VLANs and firewall rules.
What makes adding new features practical: Claude can SSH to the Pi, deploy Docker containers, call UniFi APIs, update Home Assistant automations, check logs across multiple services, and verify everything works — all through conversation. No context-switching between SSH sessions and browser tabs and UniFi controller UI.
The VLAN rule that fixed Bug 2? That was one curl command. Without Claude: log into UniFi controller, navigate to firewall settings, find traffic rules, create new rule, figure out the ordering (first-match-wins), test, verify. With Claude: “add ports 8095/8097 to the IoT allow rule.” Done.
Now when one of us wakes up at 2am, there’s a button on the nightstand. Press it silently. The podcast resumes. Thirty minutes later it stops automatically. No voice commands. No waking the other person.
The infrastructure behind that button: Audiobookshelf and Music Assistant running in Docker on a Raspberry Pi. All self-hosted. No Spotify subscriptions, no cloud podcast services, no sending our sleep patterns to Google’s servers. The podcast library, the progress tracking, the media server — all local. The complexity is managed by chungus, my Claude-powered network admin. SSH access, API calls, log inspection, automation updates — all through conversation.
Simple UX. Not simple infrastructure. But when your AI assistant can deploy Docker containers and configure firewall rules via conversation, the infrastructure becomes practical instead of aspirational.
What’s Next
The button works. The podcast plays. But Progressive Auto Insurance ads at 2am are not conducive to sleep.
Next project: Podly Pure Podcasts running on the Mac Mini. Whisper transcription to detect ad segments, small LLM to classify them, clean RSS feed generated without the ads. Audiobookshelf subscribes to Podly’s feed instead of the original. Ad-free SYSK while we sleep.
Because if you’re already self-hosting the entire podcast playback stack, might as well use a local LLM to strip the ads too.
Built with Audiobookshelf and Music Assistant. Orchestrated by Home Assistant. Debugged and deployed by Claude.
Written with Claude.