Repository: gohugoio/hugo (2026-04-08T11:26:31Z)

Issue:
Bug Report: Runtime panic (index out of range) in TOC generation with nested extension nodes
<!--
Please do not use the issue queue for questions or troubleshooting. Unless you are certain that your issue is a software defect, use the forum:

https://discourse.gohugo.io
-->

<!-- Please answer these questions before submitting your issue. Thanks! -->

### What version of Hugo are you using (`hugo version`)?

<pre>
$ hugo version
hugo v0.159.1+extended+withdeploy darwin/amd64 BuildDate=2026-03-26T09:54:15Z VendorInfo=Homebrew
</pre>

### Does this issue reproduce with the latest release?
Yes

### Description of the bug
Hugo panics with `runtime error: index out of range` during site builds when calling `.Summary` or generating a Table of Contents (TOC) for pages that contain specific Markdown extension nodes (like LaTeX/Passthrough) nested inside standard nodes (like Emphasis) within a heading.

The panic occurs in markup/goldmark/toc.go. Hugo's TOC renderer is a simplified Goldmark renderer that does not have all extensions registered. When it encounters an unknown node kind ID (e.g., from the Passthrough extension) while recursively rendering a heading's children, Goldmark's internal renderer attempts to access an out-of-bounds index in its renderer function slice.

### What did you expect to see?
The site should build successfully. The TOC renderer should gracefully handle or ignore node types it doesn't recognize.

### What did you see instead?
The build crashes with a panic:
`panic: runtime error: index out of range [41] with length 41` (The index may vary depending on the extension's Kind ID).

### Page/Config that reproduces the issue

**hugo.toml**
```toml
[markup.goldmark.extensions.passthrough]
  enable = true
[markup.goldmark.extensions.passthrough.delimiters]
  inline = [['$', '$']]
```

**content/repro.md**
```markdown
---
title: "Repro"
---
#### **$a$**
```

**layouts/_default/single.html**
```html
{{ .Summary }}
```





Discussion:
I made a PR in https://github.com/gohugoio/hugo/pull/14678. Please feel free to let me know if there is anything that needs to be changed.
log before fix:
```
(base) ➜  fix-hugo-error hugo server                            

Watching for changes in /Users/qinggo/code/fix-hugo-error/archetypes, /Users/qinggo/code/fix-hugo-error/assets, /Users/qinggo/code/fix-hugo-error/content, /Users/qinggo/code/fix-hugo-error/data, /Users/qinggo/code/fix-hugo-error/i18n, /Users/qinggo/code/fix-hugo-error/layouts, /Users/qinggo/code/fix-hugo-error/static
Watching for config changes in /Users/qinggo/code/fix-hugo-error/hugo.toml
Start building sites … 
hugo v0.159.1+extended+withdeploy darwin/amd64 BuildDate=2026-03-26T09:54:15Z VendorInfo=Homebrew

WARN  found no layout file for "html" for kind "taxonomy": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
WARN  found no layout file for "html" for kind "home": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
WARN  found no layout file for "html" for kind "page": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
Built in 13 ms
ERROR error building site: render: [en v1.0.0 guest] failed to render pages: render of "/" failed: template: rss.xml:55:22: executing "rss.xml" at <.Summary>: error calling Summary: runtime error: index out of range [41] with length 41
```

log after fix:
```
(base) ➜  fix-hugo-error /Users/qinggo/code/hugo/hugo_fixed server
Watching for changes in /Users/qinggo/code/fix-hugo-error/archetypes, /Users/qinggo/code/fix-hugo-error/assets, /Users/qinggo/code/fix-hugo-error/content, /Users/qinggo/code/fix-hugo-error/data, /Users/qinggo/code/fix-hugo-error/i18n, /Users/qinggo/code/fix-hugo-error/layouts, /Users/qinggo/code/fix-hugo-error/static
Watching for config changes in /Users/qinggo/code/fix-hugo-error/hugo.toml
Start building sites … 
hugo v0.160.0-DEV-544ae6061549792de74e8ff89c5def91cb8888b1 darwin/amd64 BuildDate=2026-03-27T10:23:01Z

WARN  found no layout file for "html" for kind "home": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
WARN  found no layout file for "html" for kind "page": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
WARN  found no layout file for "html" for kind "taxonomy": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.

                  │ EN 
──────────────────┼────
 Pages            │  4 
 Paginator pages  │  0 
 Non-page files   │  0 
 Static files     │  0 
 Processed images │  0 
 Aliases          │  0 
 Cleaned          │  0 

Built in 5 ms
Environment: "development"
Serving pages from disk
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1) 
Press Ctrl+C to stop
```
I'm pasting @QingGo 's failing test in here for reference:

```go
package hugolib

import (
	"testing"
)

// See issue 14677.
func TestTOCPanicMinimal(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = "http://example.com/"
[markup.goldmark.extensions.passthrough]
enable = true
[markup.goldmark.extensions.passthrough.delimiters]
inline = [['$', '$']]
-- content/p1.md --
---
title: p1
---
# **$a$**
-- layouts/_default/single.html --
{{ .Summary }}
`

	// This should not panic.
	// Before the fix, this would panic with "index out of range [41] with length 41"
	// (or a similar ID depending on the Goldmark version/build).
	Test(t, files)
}
```
> I'm pasting [@QingGo](https://github.com/QingGo) 's failing test in here for reference:
> 
> package hugolib
> 
> import (
> 	"testing"
> )
> 
> // See issue 14677.
> func TestTOCPanicMinimal(t *testing.T) {
> 	t.Parallel()
> 
> 	files := `
> -- hugo.toml --
> baseURL = "http://example.com/"
> [markup.goldmark.extensions.passthrough]
> enable = true
> [markup.goldmark.extensions.passthrough.delimiters]
> inline = [['$', '$']]
> -- content/p1.md --
> ---
> title: p1
> ---
> # **$a$**
> -- layouts/_default/single.html --
> {{ .Summary }}
> `
> 
> 	// This should not panic.
> 	// Before the fix, this would panic with "index out of range [41] with length 41"
> 	// (or a similar ID depending on the Goldmark version/build).
> 	Test(t, files)
> }


Thanks to @bep for implementing the proper root-cause fix, thanks to @xingzihai for the co-authorship. I believe this issue has now been fully resolved and can be closednow.



Patch:
diff --git a/markup/goldmark/convert.go b/markup/goldmark/convert.go
index f4519dff45f..8ed16b0c88f 100644
--- a/markup/goldmark/convert.go
+++ b/markup/goldmark/convert.go
@@ -122,6 +122,12 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
 		)
 	}
 
+	if cfg.Extensions.Passthrough.Enable {
+		tocRendererOptions = append(tocRendererOptions,
+			renderer.WithNodeRenderers(util.Prioritized(&tocPassthroughRenderer{}, 90)),
+		)
+	}
+
 	var (
 		extensions = []goldmark.Extender{
 			hugocontext.New(pcfg.Logger),
diff --git a/markup/goldmark/toc.go b/markup/goldmark/toc.go
index 3fb53f875c2..dd653c9e22d 100644
--- a/markup/goldmark/toc.go
+++ b/markup/goldmark/toc.go
@@ -24,6 +24,7 @@ import (
 	emojiAst "github.com/yuin/goldmark-emoji/ast"
 
 	"github.com/gohugoio/hugo-goldmark-extensions/extras"
+	"github.com/gohugoio/hugo-goldmark-extensions/passthrough"
 	"github.com/gohugoio/hugo/markup/tableofcontents"
 
 	"github.com/yuin/goldmark"
@@ -116,7 +117,9 @@ func (t *tocTransformer) Transform(n *ast.Document, reader text.Reader, pc parse
 			ast.KindAutoLink,
 			ast.KindRawHTML,
 			ast.KindText,
-			ast.KindString:
+			ast.KindString,
+			passthrough.KindPassthroughInline,
+			passthrough.KindPassthroughBlock:
 			err := t.r.Render(&headingText, reader.Source(), n)
 			if err != nil {
 				return s, err
@@ -171,6 +174,30 @@ func newTOCSanitizerPolicy() *bluemonday.Policy {
 	return p
 }
 
+// tocPassthroughRenderer renders passthrough nodes as raw text for the TOC.
+type tocPassthroughRenderer struct{}
+
+func (r *tocPassthroughRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+	reg.Register(passthrough.KindPassthroughInline, r.render)
+	reg.Register(passthrough.KindPassthroughBlock, r.render)
+}
+
+func (r *tocPassthroughRenderer) render(w util.BufWriter, src []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+	if !entering {
+		return ast.WalkContinue, nil
+	}
+	switch nn := node.(type) {
+	case *passthrough.PassthroughInline:
+		w.Write(nn.Segment.Value(src))
+	case *passthrough.PassthroughBlock:
+		for i := range nn.Lines().Len() {
+			line := nn.Lines().At(i)
+			w.Write(line.Value(src))
+		}
+	}
+	return ast.WalkSkipChildren, nil
+}
+
 var whiteSpaceRe = regexp.MustCompile(`\s+`)
 
 // sanitizeTOCHeadingTitle sanitizes s for use as a TOC heading title.


Test patch:
diff --git a/markup/goldmark/toc_integration_test.go b/markup/goldmark/toc_integration_test.go
index 715828253d4..105e1a7448c 100644
--- a/markup/goldmark/toc_integration_test.go
+++ b/markup/goldmark/toc_integration_test.go
@@ -364,3 +364,37 @@ title: home
 		`<li><a href="#1st">1^st^</a></li>`,
 	)
 }
+
+// Issue 14677
+func TestTableOfContentsWithPassthrough(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+disableKinds = ['page','rss','section','sitemap','taxonomy','term']
+[markup.goldmark.extensions.passthrough]
+  enable = true
+[markup.goldmark.extensions.passthrough.delimiters]
+  inline = [['$', '$']]
+-- content/_index.md --
+---
+title: home
+---
+## Heading with **$a$**
+
+## Heading with $b$
+
+## Heading with *$x + y$*
+
+-- layouts/home.html --
+{{ .TableOfContents }}
+`
+
+	b := hugolib.Test(t, files)
+
+	b.AssertFileContent("public/index.html",
+		`<li><a href="#heading-with">Heading with <strong>$a$</strong></a></li>`,
+		`<li><a href="#heading-with-1">Heading with $b$</a></li>`,
+		`<li><a href="#heading-with-2">Heading with <em>$x + y$</em></a></li>`,
+	)
+}