siyuan-note/siyuan · error

export source [ ] is not a regular file

Error message

export source [%s] is not a regular file

What it means

copyExportFileToDestination copies a single file to an export destination. It calls os.Lstat on the source and requires a regular file; anything else (directory, symlink target that is not regular per mode check, device, missing special file) is rejected with "export source is not a regular file" so the copy never attempts to read a non-file as one.

Solutions

  1. Pass the path of a regular file; if the source is a directory, enumerate it and call the copy per file (or use the recursive copy helper if available)
  2. Check the entry first: os.Stat / fs.existsSync + isFile() before invoking the copy
  3. Fix the configuration or plugin code that supplies the wrong path (e.g. a template directory instead of the template file)
  4. Remove or replace non-regular entries (dangling symlinks, sockets) inside the exported asset directory

Example fix

// before
copyExportFileToDestination(assetDir, destPath) // assetDir is a folder
// after
entries, _ := os.ReadDir(assetDir)
for _, e := range entries {
  if info, err := e.Info(); err == nil && info.Mode().IsRegular() {
    copyExportFileToDestination(filepath.Join(assetDir, e.Name()), filepath.Join(destPath, e.Name()))
  }
}
Defensive patterns

Strategy: validation

Validate before calling

const stat = await fs.promises.lstat(src);
if (!stat.isFile()) throw new Error(`${src} is not a regular file; enumerate and copy files individually`);

Type guard

const isRegularFile = (s) => s.isFile();

Try / catch

try {
  await copyExportFileToDestination(src, dst);
} catch (err) {
  if (String(err?.message).includes('is not a regular file')) {
    if ((await fs.promises.lstat(src)).isDirectory()) {
      return copyDirRecursive(src, dst); // handle directories explicitly
    }
    throw err;
  }
  throw err;
}

Prevention

When it happens

Trigger: Export flows (or direct calls to copyExportFileToDestination) where src points to a directory instead of a file, a dangling or special filesystem entry, or a path that was expected to be a file but is a folder after reorganizing assets/templates.

Common situations: Configured export template/asset path accidentally pointing at a directory; a plugin passing a folder path where a file path is required; assets replaced by directories after a sync; platform-specific entries (sockets/fifos) inside an export folder.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/3a52332a2834b2fc. Report an issue: GitHub.

Appendix: source

Thrown at kernel/api/export.go:900

	if err := copyExportFileToDestination(srcFullPath, dest); err != nil {
		logging.LogErrorf("copy export file [%s] to [%s] failed: %s", srcFullPath, dest, err)
		ret.Code = -1
		ret.Msg = err.Error()
		return contractFailure[apicontract.Null](ret)
	}
	return apicontract.Success(apicontract.Null{})
})

func copyExportFileToDestination(src, dest string) (err error) {
	filelock.Lock(src)
	defer filelock.Unlock(src)

	srcInfo, err := os.Lstat(src)
	if err != nil {
		return err
	}
	if !srcInfo.Mode().IsRegular() {
		return fmt.Errorf("export source [%s] is not a regular file", src)
	}

	srcFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer srcFile.Close()

	destDir := filepath.Dir(dest)
	if err = os.MkdirAll(destDir, 0755); err != nil {
		return err
	}
	tmpFile, err := os.CreateTemp(destDir, ".siyuan-export-*.tmp")
	if err != nil {
		return err
	}
	tmpPath := tmpFile.Name()
	tmpClosed := false

View on GitHub (pinned to 9f775e8a12)