gohugoio/hugo · error

failed to copy to

Error message

failed to copy %q to %q: %w

What it means

In copyContentDirsForOutput (convert.go:247-249), Hugo copies the content directory tree to the output directory using hugio.CopyDir. If any file or directory copy fails (source missing, read/write permission denied, disk full), the error is wrapped with the source and destination paths.

Solutions

  1. Check read permissions on the content source directory tree
  2. Check write permissions and disk space on the output directory
  3. Ensure files are not being modified by another process during conversion
  4. Re-run after fixing permissions or clearing the output directory

Example fix

# before: output dir read-only
hugo convert toYAML --output /mnt/readonly/out
# after
chmod -R u+w /mnt/readonly/out && hugo convert toYAML --output /mnt/readonly/out
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check read permission on source and write permission on destination.
func checkCopyFeasible(src, dst string) error {
    if info, err := os.Stat(src); err != nil || !info.IsDir() {
        return fmt.Errorf("source %q not readable", src)
    }
    return os.MkdirAll(dst, 0o755)
}

Try / catch

// Report copy failures with context and continue.
if err := hugio.CopyDir(hugofs.Os, src, dst, nil); err != nil {
    log.Printf("failed to copy %q to %q: %v", src, dst, err)
}

Prevention

When it happens

Trigger: A file in the content directory cannot be read or a file in the output directory cannot be written during the recursive copy performed by hugo convert with an --output path.

Common situations: Permission mismatch between source (read) and destination (write); files changed or removed during the copy; read-only destination; disk full.

Related errors


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

Appendix: source

Thrown at commands/convert.go:248

		skipDirs := make(map[string]bool)
		relToOutputDir, err := filepath.Rel(contentDir, outputDirAbs)
		if err == nil && relToOutputDir != ".." && !strings.HasPrefix(relToOutputDir, ".."+string(filepath.Separator)) {
			skipDirs[filepath.Clean(outputDirAbs)] = true
		}
		relToOutputContentDir, err := filepath.Rel(contentDir, outputContentDirAbs)
		if err == nil && relToOutputContentDir != ".." && !strings.HasPrefix(relToOutputContentDir, ".."+string(filepath.Separator)) {
			skipDirs[filepath.Clean(outputContentDirAbs)] = true
		}

		var shouldCopy func(filename string) bool
		if len(skipDirs) > 0 {
			shouldCopy = func(filename string) bool {
				return !skipDirs[filepath.Clean(filename)]
			}
		}

		if err := hugio.CopyDir(hugofs.Os, contentDir, outputContentDirAbs, shouldCopy); err != nil {
			return fmt.Errorf("failed to copy %q to %q: %w", contentDir, outputContentDirAbs, err)
		}
	}

	return nil
}

func (c *convertCommand) convertContents(format metadecoders.Format) error {
	if c.outputDir == "" && !c.unsafe {
		return newUserError("Unsafe operation not allowed, use --unsafe or set a different output path")
	}

	if err := c.h.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
		return err
	}

	site := c.h.Sites[0]

	workingDir := c.h.Sites[0].Deps.Conf.WorkingDir() + string(filepath.Separator)

View on GitHub (pinned to 52c9bd7908)