siyuan-note/siyuan · error

invalid export path

Error message

invalid export path

What it means

exportedFilePath reverses the '/export/' URL produced by exportedFileURL: it strips the prefix, URL-decodes the remainder, and joins it under the kernel temp export dir. If exportPath does not start with '/export/' or the encoded part is empty, it returns 'invalid export path' — the caller passed a path that never came from the bundle exporter.

Source

Thrown at kernel/model/notebook_bundle.go:188

	}
	closed = true

	finalPath := filepath.Join(util.TempDir, "export", baseFolderName+".sy.zip")
	if err = os.Remove(finalPath); nil != err && !os.IsNotExist(err) {
		logging.LogErrorf("remove previous notebook bundle failed: %s", err)
		return ""
	}
	if err = os.Rename(partialPath, finalPath); nil != err {
		logging.LogErrorf("publish notebook bundle failed: %s", err)
		return ""
	}
	return "/export/" + url.PathEscape(filepath.Base(finalPath))
}

func exportedFilePath(exportPath string) (ret string, err error) {
	encoded, ok := strings.CutPrefix(exportPath, "/export/")
	if !ok || encoded == "" {
		return "", errors.New("invalid export path")
	}
	decoded, err := url.PathUnescape(encoded)
	if nil != err {
		return "", err
	}
	ret = filepath.Join(util.TempDir, "export", filepath.FromSlash(decoded))
	if !gulu.File.IsSubPath(filepath.Join(util.TempDir, "export"), ret) {
		return "", errors.New("export path is outside export directory")
	}
	return
}

// ImportSYNotebookBundle 导入批量笔记本包。普通 .sy.zip 返回 bundle=false,由原有导入流程继续处理。
func ImportSYNotebookBundle(zipPath string) (boxIDs []string, bundle bool, err error) {
	archive, openErr := zip.OpenReader(zipPath)
	if nil != openErr {
		err = openErr
		return

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Pass the exact URL string returned by exportedFileURL, unmodified
  2. Skip entries whose path does not start with '/export/'
  3. Ensure the encoded segment is non-empty before calling

Example fix

// before
exportedFilePath(entry.path) // entry.path = 'assets/foo.png'
// after
if (strings.HasPrefix(entry.path, "/export/")) {
    exportedFilePath(entry.path)
}
Defensive patterns

Strategy: validation

Validate before calling

function isExportURL(p){ return typeof p === 'string' && p.startsWith('/export/') && p.length > '/export/'.length; }

Type guard

function asExportPath(v){ return typeof v === 'string' && v.startsWith('/export/') && v.length > 8 ? v : null; }

Try / catch

ret, err := exportedFilePath(p); if err != nil { if errors.Is(err, errInvalidExportPath) || err.Error() == "invalid export path" { skipEntry(p); return; } return err; }

Prevention

When it happens

Trigger: exportNotebooksSYBundle (or tests) calling exportedFilePath with a string lacking the '/export/' prefix, an empty suffix ('/export/'), or a hand-built relative path.

Common situations: Scripts storing/replaying export URLs after modifying them; passing a filesystem path instead of the '/export/...' URL; empty-name entries produced by upstream listing bugs.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/762eb6682e40be26. Report an issue: GitHub.