< the skull

MY RSS FEED PROBLEM (GOTOSOCIAL)
================================
2026-06-24

We expected the problem to be boring.

A small ActivityPub instance was up, the profile was public, the account had
posts, and the RSS option was enabled in the settings. The URL was exactly where
it should have been: the profile path followed by `feed.rss`. A feed reader
should have seen it, fetched it, and moved on with its quiet little life.

Instead, the feed existed but had no posts.

At first glance, everything looked normal. The endpoint returned `200 OK`. The
content type was `application/rss+xml`. The XML was valid. The channel had a
title, a link, a description, a build date, and even the profile image. But
there were no `<item>` entries. Not one. The feed was technically alive, but
empty.

That is the kind of bug that wastes time because it does not fail loudly. A 500
error gives you a direction. A 404 tells you the route is missing. Invalid XML
gives you a parser problem. But a valid RSS feed with no items politely tells
you: “There is nothing to see here.” And then you have to prove that it is
lying.

The first suspects
------------------

The obvious place to look was the reverse proxy.

The instance was behind Nginx, and feeds can be sensitive to headers, redirects,
hostnames, and content negotiation. So we checked the public endpoint first,
then bypassed the proxy and hit the service locally with the same `Host` and
forwarded headers. The result was the same in both places. That ruled out Nginx
pretty quickly.

Next we looked at the RSS setting itself. The feed was enabled in the user
interface, and when enabled it returned a valid RSS document. But after toggling
the setting off and on again, the feed briefly disappeared entirely and returned
an HTML 404 page. That was the first useful clue: the checkbox in the interface
and the value in the database were not always telling the same story.

When the setting was disabled in the database, the feed route returned 404. When
it was enabled, the feed came back as RSS. So the route was not broken. The feed
feature was not missing. It was being gated by the account setting exactly as
expected.

But even with RSS enabled, the feed was still empty.

The visibility rabbit hole
--------------------------

The next theory was visibility.

GoToSocial has a few different visibility levels, and its defaults are not
always what people coming from other Fediverse software expect. We wondered
whether the posts were not truly public. Maybe they were “unlisted” or
“unlocked,” visible on the web profile but excluded from RSS.

That seemed plausible for a while. The public profile showed the posts, the
account settings allowed public and unlisted posts to appear, and the RSS
documentation focuses on public posts. It would have been a clean explanation:
the posts were visible, but not eligible.

So we checked the database.

The statuses were local. They were federated. They were not replies. They were
not boosts. They were not pending approval. They had normal text content. Their
visibility value matched the public visibility enum for that version of
GoToSocial. In other words, the posts were not the problem. They were exactly
the kind of posts the RSS feed should include.

That theory died there.

Trying versions, chasing behavior
---------------------------------

We also pinned the GoToSocial image instead of relying on a moving tag. The
instance had been running a recent 0.21.x build, and we tried to remove
uncertainty by using a specific version. The behavior did not change.

That mattered. It meant we were not just fighting a bad container pull or a
half-updated image. The problem survived restarts and version pinning within the
same release line. We were dealing either with persistent state in the database
or a behavior in the application that depended on that state.

At this point the RSS endpoint was doing something very specific:

  * If RSS was disabled for the account, the route returned 404.
  * If RSS was enabled, the route returned valid RSS.
  * The feed included channel metadata.
  * The feed did not include any posts.
  * The public web profile could still find and render the posts.
  * The database query for obvious RSS-eligible posts returned all of them.

That narrowed the field. The posts existed. The web view could query them. The
RSS route was active. Something between “account has posts” and “RSS should
render items” was deciding that the account had no eligible latest status.

The clue hidden in account stats
--------------------------------

The real clue was not in the statuses table.

It was in the account statistics table.

The account had a nonzero status count. The posts existed. But `last_status_at`
was `NULL`.

That was the contradiction:

  statuses_count: several posts
  last_status_at: NULL

For an account with public posts, that should not happen.

Once we saw that, the empty RSS feed made sense. The RSS generator appeared to
rely on account-level cached/statistical metadata to decide whether there was a
recent eligible status for the feed. Since `last_status_at` was empty, the feed
could be generated as a valid channel with no items, even though the posts
themselves were perfectly eligible.

The public web profile did not suffer from the same problem because it queried
the statuses directly. That explained why the web profile worked while RSS did
not. They were not failing through the same code path.

The fix
-------

The fix was deliberately small.

We backed up the SQLite database. Then we updated `last_status_at` for the
account to the `created_at` value of the newest eligible public status. After
restarting the service, the RSS feed immediately populated with all the expected
items.

No proxy change. No rewrite rule. No client workaround. No fake feed. No
downgrade.

The feed went from a valid-but-empty XML document to a normal RSS feed with
every public post listed.

After that, we published more test posts. They appeared in the RSS feed as well.
The channel dates also started making sense once `last_status_at` was corrected.

So, was it a bug?
-----------------

In practical terms, yes.

Whether it is best described as a GoToSocial bug, a migration edge case, an
account stats initialization issue, or stale derived state, the result was the
same: account statistics said the account had posts, but also said it had no
last status. RSS trusted that derived state and produced an empty feed.

The important part is that the underlying posts were fine. The RSS feature was
fine. The URL was fine. The proxy was fine. The broken piece was cached account
metadata.

That is also why the problem was so slippery. Every normal check passed. The
profile loaded. The posts were public. The feed endpoint responded. The XML
validated. The database contained the posts. Nothing looked catastrophically
wrong.

Only one field was wrong, and it was not in the first table we looked at.

In GoToSocial, the public profile and RSS feed may use different internal paths
and different assumptions. If the profile shows posts but RSS is empty, checking
only the statuses is not enough.

Its that account-level derived data matters. Fields like `statuses_count` and
`last_status_at` are not just decorative. They can influence what other parts of
the application think exists.

A `200 OK` response with valid XML can still be wrong. Sometimes the bug is not
that the endpoint failed. Sometimes the bug is that it succeeded with the wrong
idea of reality.

In our case, the fix took one small database correction. Finding that correction
took a few hours of chasing ghosts: headers, visibility, settings, version tags,
profile behavior, API behavior, and finally the stats row that gave the whole
thing away.


< the skull