gohugoio/hugo · error

timeout

Error message

timeout

What it means

Returned by dispatcherPool.Execute in internal/warpc/warpc.go:247 when the 30-second timer fires before the WASM dispatcher responds, the pool shuts down, or the context is cancelled. warpc drives Hugo's out-of-process/WASM image codecs (webp, avif) and this guards every call against a hung plugin.

Solutions

  1. Reduce the input image dimensions (resize before encode) to keep the codec under the 30s budget.
  2. Check system load/memory; the WASM codec may be starved or swapping.
  3. Retry the build; transient host contention can produce a one-off timeout.
  4. If recurring, rebuild/reinstall the Hugo binary so the bundled WASM codecs are intact.

Example fix

// before
{{ $img = resources.Get | fingerprint }}
{{ $webp = $img.Resize "6000x" }}
// after (smaller workload)
{{ $webp = $img.Resize "1600x webp" }}
Defensive patterns

Strategy: retry

Validate before calling

// Cap image workload before encode so the codec stays well under 30s.
// Resize to <= ~4000px per side for webp before further processing.

Try / catch

// Retry transient timeouts once; report persistent ones.
// err = codec.Encode(...); if errors.Is(err, ...) or strings.Contains(err.Error(), "timeout") { retry }

Prevention

When it happens

Trigger: Encoding/decoding an image via a warpc codec whose WASM call does not return within 30s — e.g. an extremely large image, a deadlock in the plugin, or the host process being starved.

Common situations: Processing a very large or deeply animated source image through |webp or the avif pipeline on a constrained machine, or a corrupted WASM binary that hangs on init.

Understand the failure class

Related errors


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

Appendix: source

Thrown at internal/warpc/warpc.go:247

	if err != nil {
		return d.zeroR, err
	}

	if err := d.send(call); err != nil {
		return d.zeroR, err
	}

	timer := getTimer(30 * time.Second)
	defer putTimer(timer)

	select {
	case call = <-call.donec:
	case <-p.donec:
		return d.zeroR, p.Err()
	case <-ctx.Done():
		return d.zeroR, ctx.Err()
	case <-timer.C:
		return d.zeroR, errors.New("timeout")
	}

	if call.err != nil {
		return d.zeroR, call.err
	}

	resp, err := call.response, p.Err()

	if err == nil && resp.Header.Err != "" {
		err = errors.New(resp.Header.Err)
	}

	return resp, err
}

func (d *dispatcher[Q, R]) newCall(q Message[Q]) (*call[Q, R], error) {
	if q.Header.ID == 0 {
		q.Header.ID = d.id.Add(1)

View on GitHub (pinned to 52c9bd7908)