siyuan-note/siyuan · error
export artifact [%s] is a directory
Error message
export artifact [%s] is a directory
What it means
registerMobileExportLeaseWithID stats the artifact path before registering a lease; if the path exists but is a directory, the lease cannot be handed out (a lease is for a single exportable file) and the function returns this error wrapping the artifact path. It guards the invariant that leased export artifacts are regular files.
Source
Thrown at kernel/model/encrypted_export.go:368
}
return ""
}
func registerMobileExportLease(boxID, artifact, name, cleanupDir string) (*MobileExportLease, error) {
leaseID, err := newManagedEncryptedExportID()
if err != nil {
return nil, err
}
return registerMobileExportLeaseWithID(leaseID, boxID, artifact, name, cleanupDir)
}
func registerMobileExportLeaseWithID(leaseID, boxID, artifact, name, cleanupDir string) (*MobileExportLease, error) {
info, err := os.Stat(artifact)
if err != nil {
return nil, err
}
if info.IsDir() {
return nil, fmt.Errorf("export artifact [%s] is a directory", artifact)
}
state := &mobileExportLeaseState{
boxID: boxID,
cleanupDir: cleanupDir,
expiresAt: time.Now().Add(mobileExportLeaseTTL),
}
mobileExportLeases.Lock()
if _, exists := mobileExportLeases.leases[leaseID]; exists {
mobileExportLeases.Unlock()
return nil, fmt.Errorf("mobile export lease [%s] already exists", leaseID)
}
mobileExportLeases.leases[leaseID] = state
state.timer = time.AfterFunc(mobileExportLeaseTTL, func() {
releaseMobileExportLease(leaseID, true)
})
mobileExportLeases.Unlock()
return &MobileExportLease{ID: leaseID, Path: artifact, Name: name, Size: info.Size()}, nil
}View on GitHub (pinned to 8641553a1f)
Solutions
- os.Stat the artifact yourself before requesting the lease and reject directories with a clearer message.
- Point the lease at the concrete file inside the directory instead of the directory itself.
- Clean stale entries under temp/export (or the managed export output dir) that are directories where a file was expected.
Example fix
// before
lease, err := model.AcquireMobileExportLease("export/code")
// after
lease, err := model.AcquireMobileExportLease("export/code/main.txt") Defensive patterns
Strategy: validation
Validate before calling
if info, err := os.Stat(artifactPath); err == nil && info.IsDir() {
return fmt.Errorf("refusing to lease directory %q", artifactPath)
} Prevention
- Stat the artifact before requesting a lease
- Pass concrete files, never directories or paths with trailing separators
- Clean stale directories under temp/export
When it happens
Trigger: registerMobileExportLease / AcquireExportArtifactLease is invoked with an artifact path that resolves to a directory — e.g. temp/export/<dir>, a resolved "managed export" path pointing at a folder, or an asset path that os.Stat sees as a directory.
Common situations: A stale temp/export entry where a previous export created a folder; misconfigured managed-export resolution returning the export output directory instead of a file; passing a folder-like asset path or a path with a trailing separator.
Related errors
- export source [%s] is not a regular file
- result.Msg
- Export failed: %s
- asset path does not belong to a notebook: %s
- unsupported export path
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/89191e797e6c57b7.
Report an issue: GitHub.