A recursive CTE needs a depth guard

A cycle in the data — a category set as its own ancestor through the admin — recurses until the server stops it with an error, and the default limit is a thousand.

WITH RECURSIVE tree AS (
  SELECT id, parent_id, name, 0 AS depth
  FROM categories WHERE id = 12

  UNION ALL

  SELECT c.id, c.parent_id, c.name, t.depth + 1
  FROM categories c JOIN tree t ON c.parent_id = t.id
  WHERE t.depth < 10
)
SELECT * FROM tree ORDER BY depth, name;

The explicit depth column turns a server error into a bounded result, which is the difference between a page that fails and a page that renders ten levels. An outer LIMIT does not help, because the recursion happens before the limit is applied. For data that genuinely might contain a cycle, carrying the path and checking it for the current id is the detection — the depth guard bounds the damage and does not identify the problem.