BerriAI/litellm · warning · ValueError

RSS feed missing <channel> element

Error message

RSS feed missing <channel> element

What it means

Raised by get_blog_posts.py when parsing the LiteLLM blog RSS feed: the XML parsed successfully but has no <channel> element, so no <item> posts can be extracted. This is an internal utility (used for the 'what's new' blog lookup), not part of the completion API.

Source

Thrown at litellm/litellm_core_utils/get_blog_posts.py:78

        Fetch RSS XML from a remote URL.

        Returns the raw XML text. Raises on network errors.
        """
        response: Final = httpx.get(url, timeout=timeout)
        response.raise_for_status()
        return response.text

    @staticmethod
    def parse_rss_to_posts(xml_text: str, max_posts: int = 1) -> list[dict[str, str]]:
        """
        Parse RSS XML and return a list of blog post dicts.

        Extracts title, description, date (YYYY-MM-DD), and url from each <item>.
        """
        root: Final = ET.fromstring(xml_text)
        channel: Final = root.find("channel")
        if channel is None:
            raise ValueError("RSS feed missing <channel> element")

        posts: Final[list[dict[str, str]]] = []
        for item in channel.findall("item"):
            if len(posts) >= max_posts:
                break

            title_el = item.find("title")
            link_el = item.find("link")
            desc_el = item.find("description")
            pub_date_el = item.find("pubDate")

            if title_el is None or link_el is None:
                continue

            # Parse RFC 2822 date to YYYY-MM-DD
            date_str = ""
            if pub_date_el is not None and pub_date_el.text:
                try:

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Verify the feed URL manually with curl and inspect the root element.
  2. If the upstream feed changed format, update the parser (support Atom <feed> or the new location) or pin to the last known-good URL.
  3. For tests, make the fixture include <rss><channel><item>...</item></channel></rss>.

Example fix

# before
posts = parse_rss_to_posts('<rss></rss>')

# after
posts = parse_rss_to_posts('<rss><channel><item><title>t</title><link>l</link></item></channel></rss>')
Defensive patterns

Strategy: validation

Validate before calling

import xml.etree.ElementTree as ET

def has_channel(xml_text: str) -> bool:
    try:
        return ET.fromstring(xml_text).find('channel') is not None
    except ET.ParseError:
        return False

Try / catch

try {
  parseRssToPosts(xml);
} catch (e) {
  if (/missing <channel>/.test(e.message)) { /* skip update, use cached posts */ }
}

Prevention

When it happens

Trigger: Calling get_blog_posts / parse_rss_to_posts when the fetched URL returns XML that is valid but not RSS 2.0 — e.g. an HTML page, an Atom feed, an error page wrapped in XML, or a feed format change.

Common situations: The blog endpoint moved or changed format, a proxy/CDN returning an XML error document, or tests feeding fixture XML without a channel element.

Related errors


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/8430eed166b0f799. Report an issue: GitHub.