Filter root element of tag cloud block

Figured it out myself after all.

Instead of using the pre_render_block filter, use the render_block filter to do something like this:

add_filter('render_block', function($html, $block) {
    if ($block['blockName'] !== 'core/tag-cloud') {
        return $html;
    }

    $doc = new \DOMDocument($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
    $ps = iterator_to_array($doc->getElementsByTagName('p'));
    $p = empty($ps) ? new \DOMElement('p') : current($ps);

    return replaceChild($doc, $p, 'div', ['class' => 'tags'])->saveHTML();
}, 10, 2);

function replaceChild(\DOMDocument $doc, \DOMElement $target, string $tag, array $attrs = []) {
    $replacement = $doc->createElement($tag);

    if (!empty($attrs)) {
        foreach ($attrs as $key => $value) {
            $replacement->setAttribute($key, $value);
        }
    }

    if ($target->hasChildNodes()) {
        $children = iterator_to_array($target->childNodes);
        foreach ($children as $child) {
            $replacement->appendChild($child);
        }
    }

    $target->parentNode->replaceChild($replacement, $target);

    return $doc;
}

This should yield something like <div class="tags">...</div>.