Add: diagnosing Castopod posts that don't appear on Mastodon

Walks the four-step diagnostic chain (post created → activity delivered →
follower exists → notification semantics) for the common confusion where
a Castopod admin's auto-broadcast "doesn't show up" on a Mastodon account
they expected. Most cases are not federation bugs but the difference
between favouriting/boosting (no follow required) and following + the
fact that Mastodon notifications fire only for mentions/follows/favs/
boosts/etc., not for new posts from people you follow. Documents the bell
icon and `@`-mention escape hatches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marcus Summers 2026-05-10 00:05:18 -04:00
parent 1c17bdb60a
commit 7c566cda50
3 changed files with 162 additions and 5 deletions

View file

@ -0,0 +1,154 @@
---
title: "Castopod Posts Don't Appear on Mastodon — Diagnosing the Federation Path"
domain: troubleshooting
category: security
tags: [castopod, mastodon, fediverse, activitypub, federation, notifications]
status: published
created: 2026-05-10
updated: 2026-05-10
---
# Castopod Posts Don't Appear on Mastodon — Diagnosing the Federation Path
## 🛑 Problem
You publish a podcast episode (or a standalone post) on Castopod. The Castopod admin shows it went out fine. But on the Mastodon account that you *expected* to see it from — your own personal account, an account that follows your podcast, a colleague's — the post never shows up. Or it shows up in the home timeline but the notification bell never rings.
Three different failure modes hide behind "I didn't get the post." This article walks the diagnostic chain that distinguishes them.
---
## 🔬 The four checks, in order
Run these in sequence. The first one that fails tells you what's actually wrong.
### Check 1 — Did Castopod create the post?
On the Castopod host:
```sh
mysql -u $CP_DB_USER -p$CP_DB_PASS $CP_DB_NAME --binary-as-hex -e "
SELECT HEX(id), actor_id, LEFT(message,80), episode_id, published_at, created_at
FROM cp_fediverse_posts
ORDER BY created_at DESC LIMIT 5
"
```
If your post isn't here at all, Castopod didn't generate it. That's a Castopod-side bug — check `writable/logs/log-<date>.log`, verify the per-minute task scheduler is firing (`php spark tasks:list` should show `Last Run` for `fediverse-broadcast`), and confirm the cron exists:
```sh
sudo crontab -u www-data -l | grep tasks:run
# expect: * * * * * php /var/www/html/castopod/spark tasks:run >> /dev/null 2>&1
```
### Check 2 — Did Castopod queue and deliver the activity?
```sh
mysql -u $CP_DB_USER -p$CP_DB_PASS $CP_DB_NAME --binary-as-hex -e "
SELECT HEX(id), actor_id, type, status, scheduled_at, created_at
FROM cp_fediverse_activities
WHERE type='Create'
ORDER BY created_at DESC LIMIT 10
"
```
The `status` column tells you everything:
| Status | Meaning |
|---|---|
| `queued` | Sitting in the queue, broadcast task hasn't run yet (or is bogged down) |
| `processing` | In-flight |
| `delivered` | All follower inboxes returned 2xx |
| `failed` | One or more inbox POSTs returned non-2xx, gave up after retries |
If `status='delivered'`, Castopod has done its job — and yet someone says they didn't see the post. Move to Check 3.
### Check 3 — Are they actually a follower?
The single most common cause of "I didn't see it." Federation only delivers `Create` activities to **followers** (and to anyone explicitly mentioned). Interacting with a post (favourite, boost) does NOT establish a follow relationship.
On the Castopod host:
```sh
mysql -u $CP_DB_USER -p$CP_DB_PASS $CP_DB_NAME -e "
SELECT a.username, a.domain, f.created_at
FROM cp_fediverse_follows f
JOIN cp_fediverse_actors a ON a.id = f.actor_id
ORDER BY f.created_at DESC
"
```
`cp_fediverse_follows.actor_id` is the **follower** (remote actor); `target_actor_id` is your local podcast actor. If the user's `username@domain` isn't in this list, they don't follow your podcast, and the Create activity was never sent to their inbox.
Cross-check from the Mastodon side (if you control both):
```sh
sudo -u postgres psql mastodon_production -t -A -c "
SELECT a.username, a.domain
FROM follows f
JOIN accounts a ON a.id = f.target_account_id
WHERE f.account_id = <mastodon-account-id>
AND a.domain = '<your-castopod-domain>'
"
```
Empty result on both sides = they're not following. **Resolution: have them search `@yourpodcast@yourdomain.tld` in their Mastodon and click Follow.**
A subtler corner of this check: `accounts WHERE domain='<your-castopod-domain>'` returning 0 rows on the Mastodon side means Mastodon has never even webfingered your podcast actor. The user may have *thought* they followed at some point, but it never went through (e.g., they typed the handle wrong, or the follow request errored).
### Check 4 — Is "didn't see it" a notification problem, not a delivery problem?
Even after a successful follow, the post lands in the **home timeline** by default. Mastodon **notifications** (the bell icon, the unread badge) fire for a specific list of activity types — and "new post from someone I follow" isn't one of them. Notifications fire for:
- Mentions (`@you` in the post body)
- Follows (someone follows you)
- Favourites of your posts
- Boosts of your posts
- Polls ending
- Status edits (post you favourited was edited)
- Admin alerts
So even with delivery working perfectly and the follow in place, "I didn't get a notification on my account" is the expected state for a regular podcast post. Three ways to make notifications happen:
1. **Bell icon on the followed profile.** Mastodon UI: open the followed account's profile → click the bell. Enables per-account post notifications. Now every new post from that account raises a notification.
2. **`@`-mention in the post.** Have Castopod include `@you@yourdomain.tld` in the post text. Mention activities always raise notifications regardless of follow/bell state. (You may not control the post text on someone else's Castopod, but you control your own.)
3. **Cross-post via a different actor.** If you also run a Mastodon account for the show, post manually from there and `@`-mention the audience accounts you want to page.
---
## 🧪 Worked example
A real case: someone running a podcast on Castopod 2.0.0-next.4 expected a new episode's auto-post to appear on their personal Mastodon. It didn't.
- Check 1 → post present in `cp_fediverse_posts`, episode_id correct ✓
- Check 2 → matching `cp_fediverse_activities` row, `type='Create'`, `status='delivered'`
- Check 3 → 8 followers in `cp_fediverse_follows`, none from the personal Mastodon's domain ✗
Outcome: the user wasn't following their own podcast. They had been favouriting and boosting its posts (which doesn't require following), and assumed those interactions implied a follow. Resolution: search-and-follow from the personal Mastodon. After the follow propagated, future broadcasts arrived as expected.
The post itself never raised a notification (only landed in home timeline). They later enabled the bell icon on the podcast profile and started getting notified on new episodes.
---
## 🧭 When this isn't the answer
If Check 3 shows the person IS a follower but they still didn't receive the post:
- **Check their inbox** if you have access: Mastodon nginx access log:
```sh
sudo grep '<castopod-ip-or-domain>' /var/log/nginx/access.log | grep inbox
```
Expect a `POST /users/<them>/inbox HTTP/2.0 202` from the Castopod IP shortly after `published_at`. No POST = Castopod didn't deliver despite claiming `status='delivered'` (rare; check Castopod's HTTP signing config and any outbound firewall on the Castopod host).
- **Check Sidekiq** on Mastodon for `ActivityPub::ProcessingWorker` failures around the activity timestamp.
- **Check domain blocks**: `SELECT * FROM domain_blocks WHERE domain = '<castopod-domain>'` on Mastodon. A silenced or suspended domain on either end would explain everything.
---
## 📚 References
- ActivityPub spec — [Delivery semantics](https://www.w3.org/TR/activitypub/#delivery): `Create` activities go to actors in `to`/`cc`/`bcc`/`audience`; for public posts that resolves to the actor's followers collection.
- Mastodon notification types: `app/models/notification.rb``TYPES` constant
- Castopod fediverse module: `modules/Fediverse/Commands/Broadcast.php` (the per-minute task) and `modules/Fediverse/Models/ActivityModel.php` (queue model)
- Related: [Castopod: Stale Federated Avatar URLs After Remote Profile Updates](castopod-stale-federated-avatar.md) — sister article, also Castopod fediverse module

View file

@ -1,6 +1,6 @@
---
created: 2026-04-02T16:03
updated: 2026-05-08T01:08
updated: 2026-05-10T00:10
---
* [Home](index.md)
* [Linux & Sysadmin](01-linux/index.md)
@ -77,6 +77,7 @@ updated: 2026-05-08T01:08
* [Custom Fail2ban Jail: Apache Directory Scanning](05-troubleshooting/security/apache-dirscan-fail2ban-jail.md)
* [Tuning Netdata `web_log_1m_successful` for Redirect-Heavy WordPress Sites](05-troubleshooting/security/netdata-web-log-successful-redirect-heavy-tuning.md)
* [Castopod: Stale Federated Avatar URLs After Remote Profile Updates](05-troubleshooting/security/castopod-stale-federated-avatar.md)
* [Castopod Posts Don't Appear on Mastodon — Diagnosing the Federation Path](05-troubleshooting/security/castopod-broadcast-not-on-mastodon.md)
* [Nextcloud AIO Unhealthy 20h After Nightly Update](05-troubleshooting/docker/nextcloud-aio-unhealthy-20h-stuck.md)
* [n8n Behind Reverse Proxy: X-Forwarded-For Trust Fix](05-troubleshooting/docker/n8n-proxy-trust-x-forwarded-for.md)
* [Docker & Caddy Recovery After Reboot (Fedora + SELinux)](05-troubleshooting/docker-caddy-selinux-post-reboot-recovery.md)

View file

@ -1,13 +1,13 @@
---
created: 2026-04-06T09:52
updated: 2026-05-08T01:08
updated: 2026-05-10T00:10
---
# MajorLinux Tech Wiki — Index
> A growing reference of Linux, self-hosting, open source, streaming, and troubleshooting guides. Written by MajorLinux. Used by MajorTwin.
>
> **Last updated:** 2026-05-08
> **Article count:** 108
> **Last updated:** 2026-05-10
> **Article count:** 109
## Domains
@ -17,7 +17,7 @@ updated: 2026-05-08T01:08
| 🏠 Self-Hosting & Homelab | `02-selfhosting/` | 39 |
| 🔓 Open Source Tools | `03-opensource/` | 10 |
| 🎙️ Streaming & Podcasting | `04-streaming/` | 2 |
| 🔧 General Troubleshooting | `05-troubleshooting/` | 45 |
| 🔧 General Troubleshooting | `05-troubleshooting/` | 46 |
---
@ -202,6 +202,7 @@ updated: 2026-05-08T01:08
- [Custom Fail2ban Jail: Apache Directory Scanning & Junk Methods](05-troubleshooting/security/apache-dirscan-fail2ban-jail.md)
- [Tuning Netdata `web_log_1m_successful` for Redirect-Heavy WordPress Sites](05-troubleshooting/security/netdata-web-log-successful-redirect-heavy-tuning.md)
- [Castopod: Stale Federated Avatar URLs After Remote Profile Updates](05-troubleshooting/security/castopod-stale-federated-avatar.md)
- [Castopod Posts Don't Appear on Mastodon — Diagnosing the Federation Path](05-troubleshooting/security/castopod-broadcast-not-on-mastodon.md)
### Storage
- [mdadm RAID Recovery After USB Hub Disconnect](05-troubleshooting/storage/mdadm-usb-hub-disconnect-recovery.md)
@ -216,6 +217,7 @@ updated: 2026-05-08T01:08
| Date | Article | Domain |
|---|---|---|
| 2026-05-10 | [Castopod Posts Don't Appear on Mastodon — Diagnosing the Federation Path](05-troubleshooting/security/castopod-broadcast-not-on-mastodon.md) | Troubleshooting |
| 2026-05-08 | [Castopod: Stale Federated Avatar URLs After Remote Profile Updates](05-troubleshooting/security/castopod-stale-federated-avatar.md) | Troubleshooting |
| 2026-05-08 | [Tuning Netdata `web_log_1m_successful` for Redirect-Heavy WordPress Sites](05-troubleshooting/security/netdata-web-log-successful-redirect-heavy-tuning.md) | Troubleshooting |
| 2026-05-07 | [Mastodon — The `--prune-profiles` Trap and How to Recover](02-selfhosting/services/mastodon-prune-profiles-trap.md) | Self-Hosting |