Agent session embeds: transcript table-of-contents fragment links are dead (headings render without ids)
Describe the bug
Agent session embeds render transcript headings without id attributes, so fragment links inside the embed are dead. Coding agents (Claude Code, Codex, Gemini CLI, ...) frequently emit a table of contents in their transcripts, e.g.:
## Intro
- [Astro vs WordPress: the practical comparison](#astro-vs-wordpress-the-practical-comparison)
- [Test editing before choosing the stack](#test-editing-before-choosing-the-stack)Inside an {% agent_session %} embed every one of those links points at #<slug>, but no element in the page carries that id — the headings render as bare <h2>...</h2>. Clicking does nothing (or jumps the page). In a real writing-session transcript I embedded, 9 of 9 table-of-contents links were dead.
To Reproduce
- Upload an agent session whose assistant message contains a markdown table of contents with
#slugfragment links plus matching## headings(the format agents commonly generate). - Embed it in an article:
{% agent_session <id> %}. - Open the article preview.
- Inspect the embed: the
<h2>elements have noidand no anchor child; the TOC<a href="#...">targets do not exist in the DOM. - Click a TOC link: nothing navigates to the intended section.
Expected behavior
Either headings in agent session embeds get (scoped) ids so fragment links resolve within the embed, or links without a resolvable target degrade visibly. Ideally a click also expands collapsed long text (agent-session-text-collapse) and scrolls the embed's own .agent-session-scroll container instead of jumping the page.
Root cause
AgentSessionRenderers::MarkdownRenderer (app/services/agent_session_renderers/markdown_renderer.rb) uses Redcarpet::Render::HTML without heading ids, and the sanitizer whitelist (ALLOWED_ATTRS = %w[class href target rel]) would strip id even if the renderer emitted one. By contrast, article markdown goes through Redcarpet::Render::HTMLRouge#header which does slugify headings. Note the slug conventions differ: agent transcripts follow the GitHub convention (hyphens preserved, e.g. #work-through-a-small-business-example), while HTMLRouge's slugify strips punctuation including hyphens — so a fix should slug headings GitHub-style to match what agents actually generate.
Two additional considerations for a fix:
- ids must be scoped per embed (e.g.
agent-session-<id>-<slug>) so they cannot collide with article headings or with a second session embed on the same page; - the same heading text may repeat across messages of one session, which needs GitHub-style dedup suffixes (
-2,-3).
Screenshots
Not applicable — pure DOM/behaviour issue, fully described above.
Desktop (please complete the following information):
- OS, version: Arch Linux
- Browser, version: Chrome 144
Smartphone (please complete the following information):
- Device: n/a
- OS, version: n/a
- Browser, version: n/a
Additional context
Verified against current main (Forem revision with AgentSessionTag + MarkdownRenderer as shipped in the Agent Sessions feature, #22854). Related but different: #22703 covers cross-page anchor links; #4922 covers duplicate heading anchors in articles. Neither covers fragment links inside ltag-agent-session embeds.
Source: forem/forem