gohugoio/hugo · error

failed to save file

Error message

failed to save file %q: %s

What it means

After conversion, the importer writes the new Markdown to `targetFile` via `helpers.WriteToDisk`. This wraps a filesystem write failure for the converted post; `%q` is the source (Jekyll) filename, `%s` the underlying I/O error.

Solutions

  1. Confirm the target directory exists and is writable (`-w` check).
  2. Free disk space or move the target to a volume with capacity.
  3. Re-run the import after correcting permissions on the destination.
  4. Use an absolute `--target` path under a directory you own.

Example fix

// before
// $ hugo import jekyll ~/blog ~/hugo --target=/srv/readonly
// after
// $ hugo import jekyll ~/blog ~/hugo --target=/home/me/hugo-content
Defensive patterns

Strategy: validation

Validate before calling

// Verify the target directory is writable before importing.
func validateTargetDir(dir string) error {
  fi, err := os.Stat(dir)
  if err != nil { return err }
  if !fi.IsDir() { return fmt.Errorf("%s is not a directory", dir) }
  f, err := os.CreateTemp(dir, ".hugoimp-*")
  if err != nil { return err }
  f.Close(); os.Remove(f.Name())
  return nil
}

Type guard

null

Try / catch

// On write failure, retry once with a fresh path, then report.
if err := helpers.WriteToDisk(targetFile, r, fs); err != nil {
  if errors.Is(err, os.ErrPermission) { /* fix perms / pick new target */ }
}

Prevention

When it happens

Trigger: Fires when `WriteToDisk` cannot create/write the target file — destination directory not creatable, out of disk space, permission denied, or a read-only filesystem.

Common situations: Importing into a `--target` directory on a read-only volume; running out of disk during a large import; a target path that conflicts with an existing file marked read-only; CI without write access to the output dir.

Related errors


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

Appendix: source

Thrown at commands/import.go:337

		return err
	}
	pf, err := pageparser.ParseFrontMatterAndContent(bytes.NewReader(contentBytes))
	if err != nil {
		return fmt.Errorf("failed to parse file %q: %s", filename, err)
	}
	newmetadata, err := c.convertJekyllMetaData(pf.FrontMatter, postName, postDate, draft)
	if err != nil {
		return fmt.Errorf("failed to convert metadata for file %q: %s", filename, err)
	}

	content, err := c.convertJekyllContent(newmetadata, string(pf.Content))
	if err != nil {
		return fmt.Errorf("failed to convert content for file %q: %s", filename, err)
	}

	fs := hugofs.Os
	if err := helpers.WriteToDisk(targetFile, strings.NewReader(content), fs); err != nil {
		return fmt.Errorf("failed to save file %q: %s", filename, err)
	}
	return nil
}

func (c *importCommand) copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyllPostDirs map[string]bool) (err error) {
	fs := hugofs.Os

	fi, err := fs.Stat(jekyllRoot)
	if err != nil {
		return err
	}
	if !fi.IsDir() {
		return errors.New(jekyllRoot + " is not a directory")
	}
	err = os.MkdirAll(dest, fi.Mode())
	if err != nil {
		return err
	}

View on GitHub (pinned to 52c9bd7908)