{"record":{"id":"89191e797e6c57b7","repo":"siyuan-note/siyuan","slug":"export-artifact-s-is-a-directory","errorCode":null,"errorMessage":"export artifact [%s] is a directory","messagePattern":"export artifact \\[(.+?)\\] is a directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/encrypted_export.go","lineNumber":368,"sourceCode":"\t}\n\treturn \"\"\n}\n\nfunc registerMobileExportLease(boxID, artifact, name, cleanupDir string) (*MobileExportLease, error) {\n\tleaseID, err := newManagedEncryptedExportID()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn registerMobileExportLeaseWithID(leaseID, boxID, artifact, name, cleanupDir)\n}\n\nfunc registerMobileExportLeaseWithID(leaseID, boxID, artifact, name, cleanupDir string) (*MobileExportLease, error) {\n\tinfo, err := os.Stat(artifact)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif info.IsDir() {\n\t\treturn nil, fmt.Errorf(\"export artifact [%s] is a directory\", artifact)\n\t}\n\tstate := &mobileExportLeaseState{\n\t\tboxID:      boxID,\n\t\tcleanupDir: cleanupDir,\n\t\texpiresAt:  time.Now().Add(mobileExportLeaseTTL),\n\t}\n\tmobileExportLeases.Lock()\n\tif _, exists := mobileExportLeases.leases[leaseID]; exists {\n\t\tmobileExportLeases.Unlock()\n\t\treturn nil, fmt.Errorf(\"mobile export lease [%s] already exists\", leaseID)\n\t}\n\tmobileExportLeases.leases[leaseID] = state\n\tstate.timer = time.AfterFunc(mobileExportLeaseTTL, func() {\n\t\treleaseMobileExportLease(leaseID, true)\n\t})\n\tmobileExportLeases.Unlock()\n\treturn &MobileExportLease{ID: leaseID, Path: artifact, Name: name, Size: info.Size()}, nil\n}","sourceCodeStart":350,"sourceCodeEnd":386,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/encrypted_export.go#L350-L386","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlease, err := model.AcquireMobileExportLease(\"export/code\")\n// after\nlease, err := model.AcquireMobileExportLease(\"export/code/main.txt\")","handlingStrategy":"validation","validationCode":"if info, err := os.Stat(artifactPath); err == nil && info.IsDir() {\n    return fmt.Errorf(\"refusing to lease directory %q\", artifactPath)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Stat the artifact before requesting a lease","Pass concrete files, never directories or paths with trailing separators","Clean stale directories under temp/export"],"tags":["filesystem","export","validation"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}