gohugoio/hugo · error

failed to open source: %w

Error message

failed to open source: %w

What it means

Wrapped error from `resourceHash.init` (resource.go:668-689) when the underlying `ReadSeekCloser()` provider fails to open the resource's source bytes. Hugo needs the byte stream to compute the xxhash used as the resource identity/cache key. The error from `l.ReadSeekCloser()` at resource.go:673-675 is wrapped and returned once (guarded by `sync.Once`).

Source

Thrown at resources/resource.go:675

type isPublishedProvider interface {
	isPublished() bool
}

type resourceHash struct {
	value    uint64
	size     int64
	initOnce sync.Once
}

func (r *resourceHash) init(l hugio.ReadSeekCloserProvider) error {
	var initErr error
	r.initOnce.Do(func() {
		var hash uint64
		var size int64
		f, err := l.ReadSeekCloser()
		if err != nil {
			initErr = fmt.Errorf("failed to open source: %w", err)
			return
		}
		defer f.Close()
		hash, size, err = hashImage(f)
		if err != nil {
			initErr = fmt.Errorf("failed to calculate hash: %w", err)
			return
		}
		r.value = hash
		r.size = size
	})

	return initErr
}

func hashImage(r io.ReadSeeker) (uint64, int64, error) {
	return hashing.XXHashFromReader(r)
}

View on GitHub (pinned to 52c9bd7908)

Solutions

  1. Verify the source file exists and is readable at the path Hugo resolved (`hugo config | grep resourceDir`, check perms).
  2. For symlinks, confirm the target is intact and within the project root.
  3. Re-run `hugo --gc` to clear stale cache entries that reference moved files.
  4. For remote resources, ensure the body factory returns a fresh reader each call (don't reuse a consumed `io.Reader`).
  5. If using a custom resource type, ensure its `ReadSeekCloser()` returns a non-nil error only on genuine I/O failure.

Example fix

// custom resource provider -- before
func (r *myRes) ReadSeekCloser() (hugio.ReadSeekCloser, error) {
    return r.consumedReader, nil
}

// after
func (r *myRes) ReadSeekCloser() (hugio.ReadSeekCloser, error) {
    return hugio.NewReadSeekerNoOpCloser(bytes.NewReader(r.bytes)), nil
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Before hashing, verify the source path exists.
import "os"
func ensureReadable(p string) error {
	f, err := os.Open(p)
	if err != nil { return err }
	f.Close()
	return nil
}

Try / catch

// Wrap resource creation; log and skip unopenable sources.
r, err := rs.NewResource(...)
if err != nil {
	if strings.Contains(err.Error(), "failed to open source") {
		logger.Warnf("skip missing resource %s: %v", path, err)
		continue
	}
	return err
}

Prevention

When it happens

Trigger: Any code path that triggers lazy hash computation on a Resource whose source can't be opened: missing file on disk, broken symlink, remote resource whose body is already consumed/closed, page bundle path moved after registration, or a `ReadSeekCloser` factory returning an error.

Common situations: External process deleted a `static/` or `assets/` file between Hugo startup and hash computation; symlink to NAS dropped; concurrent `hugo mod` clean removing a module asset; custom resource provider returning an error from its opener; remote resource body closed before re-read.

Related errors


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