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
- Check read permissions on the content source directory tree
- Check write permissions and disk space on the output directory
- Ensure files are not being modified by another process during conversion
- 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
- Ensure the content source and output directories are both accessible by the Hugo user
- Close other programs that might lock content files during conversion
- Run hugo convert from a stable working tree
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
- failed to save file
- failed to create workingDir
- failed to resolve output path
- already exists
- already exists and is not empty. See --force.
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)