I found a solution that is working for me to the unknown playlist format issue with the help of chat GPT.
Tvheadend does not accept quotes on the second line of a channel entry.
Like this:
pipe://streamlink 'https://pluto.tv/live-tv/51c75f7bb6f26ba1cd00002f' best --stdout
They must be:
pipe://streamlink https://pluto.tv/live-tv/51c75f7bb6f26ba1cd00002f best --stdout
also Tvheadend expects tvg-id (lower-case with a dash), not tvg-ID.
 
The script needed to have the playlist build lines changed to
 
    f'#EXTINF:-1 tvg-id="pluto-{channel["_id"]}" tvg-chno="{channel["number"]}" '
    f'tvg-name="{channel["name"]}" tvg-logo="{channel.get("colorLogoPNG", {}).get("path", "")}",'
    f'{channel["name"]}\n'
)
m3u += (
    f"pipe://streamlink https://pluto.tv/live-tv/{channel['_id']} best --stdout\n"
)
So the complete updated script with the changes made need to be this:
import requests
from datetime import datetime, timedelta, timezone
import xml.etree.ElementTree as ET
import socket
import io
# CONFIG -----------------------------------------------------------------------------
SOCKET = "/config/epggrab/xmltv.sock"  # Path to your Tvheadend XMLTV UNIX socket
# ------------------------------------------------------------------------------------
now = datetime.now(timezone.utc)
# round the start time down to the nearest 30 min
start_time = now - timedelta(
    minutes=now.minute % 30, seconds=now.second, microseconds=now.microsecond
)
stop_time = start_time + timedelta(hours=8)
epg_begin = start_time.strftime("%Y-%m-%dT%H:%M:%S-00:00")
epg_end = stop_time.strftime("%Y-%m-%dT%H:%M:%S-00:00")
data = requests.get(
    f"https://api.pluto.tv/v2/channels?start={epg_begin}&stop={epg_end}"
).json()
m3u = "#EXTM3U\n"
root = ET.Element("tv")
programmes = []
for channel in data:
    # ----- XMLTV channel element -----
    chan_id = f"pluto-{channel['_id']}"
    channel_element = ET.SubElement(root, "channel", id=chan_id)
    ET.SubElement(channel_element, "display-name").text = channel["name"]
    ET.SubElement(channel_element, "channel-number").text = str(channel["number"])
    ET.SubElement(channel_element, "icon").text = channel.get("colorLogoPNG", {}).get(
        "path", ""
    )
    # ----- M3U entry -----
    logo = channel.get("colorLogoPNG", {}).get("path", "")
    m3u += (
        f'#EXTINF:-1 tvg-id="{chan_id}" '
        f'tvg-chno="{channel["number"]}" '
        f'tvg-name="{channel["name"]}" '
        f'tvg-logo="{logo}",'
        f'{channel["name"]}\n'
    )
    # NOTE: no quotes around URL
    m3u += (
        f"pipe://streamlink https://pluto.tv/live-tv/{channel['_id']} best --stdout\n"
    )
    # ----- Programme list -----
    for programme in channel.get("timelines", []):
        programmes.append(
            {
                "start": programme["start"],
                "stop": programme["stop"],
                "channel": chan_id,
                "title": programme.get("title", "Untitled"),
                "episode": programme.get("episode", {}),
            }
        )
# Build XMLTV EPG
for programme in programmes:
    start_str = datetime.fromisoformat(programme["start"]).strftime(
        "%Y%m%d%H%M%S +0000"
    )
    stop_str = datetime.fromisoformat(programme["stop"]).strftime("%Y%m%d%H%M%S +0000")
    p = ET.SubElement(
        root,
        "programme",
        start=start_str,
        stop=stop_str,
        channel=programme["channel"],
    )
    ep = programme["episode"]
    ET.SubElement(p, "title").text = programme["title"]
    ET.SubElement(p, "sub-title").text = ep.get("name", "")
    ET.SubElement(p, "desc").text = ep.get("description", "")
    ET.SubElement(p, "episode-num").text = f"S{ep.get('season', 0)}E{ep.get('number', 0)}"
    ET.SubElement(p, "category").text = ep.get("genre", "")
    ET.SubElement(p, "category").text = ep.get("subGenre", "")
    ET.SubElement(p, "category").text = programme.get("category", "")
tree = ET.ElementTree(root)
ET.indent(tree, space="\t", level=0)
# Send EPG to Tvheadend’s XMLTV socket
xml_bytes = io.BytesIO()
tree.write(xml_bytes, encoding="UTF-8", xml_declaration=True)
try:
    with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
        s.connect(SOCKET)
        xml_bytes.seek(0)
        s.sendall(xml_bytes.read())
except Exception:
    # You can uncomment these lines for debugging
    # with open("pluto.xmltv", "wb") as f:
    #     tree.write(f, encoding="utf-8", xml_declaration=True)
    pass
# Print the playlist to stdout — Tvheadend reads this
print(m3u, end="")
Hopefully this can help someone out