gohugoio/hugo · error

html/template: cannot Clone %q after it has executed

Error message

html/template: cannot Clone %q after it has executed

What it means

Thrown by html/template CloneShallow (Hugo's shallow-clone helper) when the template has already been escaped/executed. html/template freezes its escaping state after the first Execute (escapeErr becomes non-nil), and cloning an already-escaped template would copy an inconsistent escaping namespace, so CloneShallow refuses. The %q is the offending template's Name().

Source

Thrown at tpl/internal/go_templates/htmltemplate/hugo_template.go:76

}

func indirect(a any) any {
	in := doIndirect(a)

	// We have a special Result type that we want to unwrap when printed.
	if pp, ok := in.(types.PrintableValueProvider); ok {
		return pp.PrintableValue()
	}

	return in
}

// CloneShallow creates a shallow copy of the template. It does not clone  or copy the nested templates.
func (t *Template) CloneShallow() (*Template, error) {
	t.nameSpace.mu.Lock()
	defer t.nameSpace.mu.Unlock()
	if t.escapeErr != nil {
		return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
	}
	textClone, err := t.text.Clone()
	if err != nil {
		return nil, err
	}
	ns := &nameSpace{set: make(map[string]*Template)}
	ns.esc = makeEscaper(ns)
	ret := &Template{
		nil,
		textClone,
		textClone.Tree,
		ns,
	}
	ret.set[ret.Name()] = ret

	// Return the template associated with the name of this template.
	return ret.set[ret.Name()], nil
}

View on GitHub (pinned to 52c9bd7908)

Solutions

  1. Call CloneShallow before the first Execute/ExecuteTemplate/Prepare on the template or any of its associated siblings.
  2. If you must branch after rendering, rebuild the template from scratch with New(name) + Parse instead of cloning.
  3. Clone once at startup to produce the base variant, then Execute the clones (each clone gets its own escaping state).
  4. Audit call sites to ensure no shared template is both executed and later shallow-cloned.

Example fix

// before
t.Execute(os.Stdout, data)
clone, err := t.CloneShallow() // error: already executed

// after
clone, err := t.CloneShallow() // clone first
if err != nil { return err }
t.Execute(os.Stdout, data)      // execute original (or the clone)
Defensive patterns

Strategy: validation

Validate before calling

// Clone before any Execute. Track executed state explicitly.
var executed bool
func render(t *template.Template, w io.Writer, data any) error {
    if !executed {
        base, err := t.CloneShallow()
        if err != nil {
            return fmt.Errorf("clone-before-execute: %w", err)
        }
        _ = base
    }
    executed = true
    return t.Execute(w, data)
}

Try / catch

clone, err := t.CloneShallow()
if err != nil {
    // rebuild from scratch instead of cloning an executed template
    t = template.New(t.Name())
    _, err = t.Parse(originalBody)
    if err != nil { return err }
    clone, err = t.CloneShallow()
}

Prevention

When it happens

Trigger: Calling (*htmltemplate.Template).CloneShallow() on a Template whose escapeErr field is set (non-nil and not the escapeOK sentinel). escapeErr is set the moment escape() runs during Execute/ExecuteTemplate/Prepare on the template or any template sharing its nameSpace.

Common situations: Hugo rebuilds that try to reuse and clone an already-rendered template set; a long-running server that renders once then tries to clone the same instance for per-request variants; helper code that assumes a template can be cloned repeatedly.

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/24802d9db1547bbc. Report an issue: GitHub.