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>
154 lines
7.7 KiB
Markdown
154 lines
7.7 KiB
Markdown
---
|
|
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
|