{"record":{"id":"b82dc07fe782aab5","repo":"siyuan-note/siyuan","slug":"marketplace-package-already-exists","errorCode":null,"errorMessage":"marketplace package already exists","messagePattern":"marketplace package already exists","errorType":"exception","errorClass":"ErrLocalBazaarPackageExists","httpStatus":null,"severity":"warning","filePath":"kernel/model/bazaar.go","lineNumber":110,"sourceCode":"\tmeta installMeta\n}\n\n// ThemeInstallOptions 描述新安装主题后需要应用的外观模式\ntype ThemeInstallOptions struct {\n\tMode   int\n\tModeOS bool\n}\n\n// LocalBazaarPackageInstallResult 描述本地集市包的识别和安装结果。\ntype LocalBazaarPackageInstallResult struct {\n\tPackageType   string `json:\"packageType\"`\n\tPackageName   string `json:\"packageName\"`\n\tMinAppVersion string `json:\"minAppVersion,omitempty\"`\n\tUpdated       bool   `json:\"updated\"`\n}\n\nvar (\n\tErrLocalBazaarPackageExists       = errors.New(\"marketplace package already exists\")\n\tErrLocalBazaarPackageIncompatible = errors.New(\"marketplace package is incompatible\")\n\tlocalBazaarInstallLock            sync.Mutex\n)\n\n// updatePackages 更新一组集市包；同类型批量更新时，安装后处理只执行一次\nfunc updatePackages(packages []*UpdatedPackage, pkgType string, successCount, failedCount *int, planned int) {\n\titems := make([]batchInstallItem, 0, len(packages))\n\tfor _, updated := range packages {\n\t\tpkg := updated.Available\n\t\tmeta, err := installBazaarPackage(pkgType, pkg.RepoURL, pkg.RepoHash, pkg.Name)\n\t\tif err != nil {\n\t\t\tlogging.LogErrorf(\"update %s [%s] failed: %s\", pkgType, pkg.Name, err)\n\t\t\tutil.PushErrMsg(fmt.Sprintf(Conf.language(238), pkg.Name), 5000)\n\t\t\t*failedCount++\n\t\t\tcontinue\n\t\t}\n\t\titems = append(items, batchInstallItem{name: pkg.Name, meta: meta})\n\t\t*successCount++","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/model/bazaar.go#L92-L128","documentation":"Sentinel error ErrLocalBazaarPackageExists returned by model.InstallLocalBazaarPackage (kernel/model/bazaar.go) when installing a local marketplace package whose install path already exists on disk (os.Lstat succeeds, so result.Updated is true) and the caller passed overwrite=false. It protects an already-installed plugin/theme/icon/widget/template from being silently replaced by an imported archive. It is a deliberate, recoverable refusal, not a defect: the UI is expected to catch it and ask the user whether to overwrite.","triggerScenarios":"Calling model.InstallLocalBazaarPackage(archivePath, frontend, false) for a package whose directory already exists under data/plugins/, data/widgets/, data/templates/, appearance/themes/, or appearance/icons/ (e.g. re-importing a theme zip that is already installed). Any HTTP handler for local package import that forwards overwrite=false hits it whenever the package name resolves to an existing install path.","commonSituations":"User downloads a theme/plugin zip and imports it while an older copy is still installed; reinstalling a package after a failed uninstall left the directory behind; importing a package whose manifest name field collides with an installed one.","solutions":["Pass overwrite=true in the InstallLocalBazaarPackage call when the user confirmed replacing the existing package (the result.Updated flag then drives update semantics such as plugin reload instead of fresh install)","Uninstall the existing package first (model.UninstallPackage) and then import with overwrite=false","If the leftover directory is from a failed uninstall, remove it manually from data/plugins/<name> (or the matching themes/icons/widgets/templates folder) and retry the import"],"exampleFix":"// before\nresult, err := model.InstallLocalBazaarPackage(archivePath, frontend, false)\nif err != nil {\n    return err // surfaces \"marketplace package already exists\"\n}\n\n// after\nresult, err := model.InstallLocalBazaarPackage(archivePath, frontend, false)\nif errors.Is(err, model.ErrLocalBazaarPackageExists) {\n    // ask the user, then retry with overwrite=true\n    result, err = model.InstallLocalBazaarPackage(archivePath, frontend, true)\n}\nif err != nil {\n    return err\n}","handlingStrategy":"validation","validationCode":"infos, _, _, err := model.GetInstalledPackageInfos(pkgType)\nif err != nil {\n    return err\n}\nexists := false\nfor _, info := range infos {\n    if info.Pkg.Name == pkgName {\n        exists = true\n        break\n    }\n}\nif exists && !userConfirmedOverwrite {\n    return errors.New(\"ask user: overwrite existing package?\")\n}","typeGuard":"func isPackageExistsErr(err error) bool {\n    return errors.Is(err, model.ErrLocalBazaarPackageExists)\n}","tryCatchPattern":"result, err := model.InstallLocalBazaarPackage(archivePath, frontend, false)\nif err != nil {\n    if errors.Is(err, model.ErrLocalBazaarPackageExists) {\n        // prompt user; on confirm retry with overwrite=true\n    }\n    return err\n}","preventionTips":["Always pass overwrite=false on first attempt and treat ErrLocalBazaarPackageExists as a prompt, not a failure","Check GetInstalledPackageInfos before importing so the UI can offer 'update' instead of 'install' semantics"],"tags":["marketplace","bazaar","package-install","already-exists","sentinel-error"],"backgroundTag":"package-already-exists","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}