siyuan-note/siyuan · warning · ErrLocalBazaarPackageExists

marketplace package already exists

Error message

marketplace package already exists

What it means

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.

Source

Thrown at kernel/model/bazaar.go:110

	meta installMeta
}

// ThemeInstallOptions 描述新安装主题后需要应用的外观模式
type ThemeInstallOptions struct {
	Mode   int
	ModeOS bool
}

// LocalBazaarPackageInstallResult 描述本地集市包的识别和安装结果。
type LocalBazaarPackageInstallResult struct {
	PackageType   string `json:"packageType"`
	PackageName   string `json:"packageName"`
	MinAppVersion string `json:"minAppVersion,omitempty"`
	Updated       bool   `json:"updated"`
}

var (
	ErrLocalBazaarPackageExists       = errors.New("marketplace package already exists")
	ErrLocalBazaarPackageIncompatible = errors.New("marketplace package is incompatible")
	localBazaarInstallLock            sync.Mutex
)

// updatePackages 更新一组集市包;同类型批量更新时,安装后处理只执行一次
func updatePackages(packages []*UpdatedPackage, pkgType string, successCount, failedCount *int, planned int) {
	items := make([]batchInstallItem, 0, len(packages))
	for _, updated := range packages {
		pkg := updated.Available
		meta, err := installBazaarPackage(pkgType, pkg.RepoURL, pkg.RepoHash, pkg.Name)
		if err != nil {
			logging.LogErrorf("update %s [%s] failed: %s", pkgType, pkg.Name, err)
			util.PushErrMsg(fmt.Sprintf(Conf.language(238), pkg.Name), 5000)
			*failedCount++
			continue
		}
		items = append(items, batchInstallItem{name: pkg.Name, meta: meta})
		*successCount++

View on GitHub (pinned to afa823b6b4)

Solutions

  1. 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)
  2. Uninstall the existing package first (model.UninstallPackage) and then import with overwrite=false
  3. 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

Example fix

// before
result, err := model.InstallLocalBazaarPackage(archivePath, frontend, false)
if err != nil {
    return err // surfaces "marketplace package already exists"
}

// after
result, err := model.InstallLocalBazaarPackage(archivePath, frontend, false)
if errors.Is(err, model.ErrLocalBazaarPackageExists) {
    // ask the user, then retry with overwrite=true
    result, err = model.InstallLocalBazaarPackage(archivePath, frontend, true)
}
if err != nil {
    return err
}
Defensive patterns

Strategy: validation

Validate before calling

infos, _, _, err := model.GetInstalledPackageInfos(pkgType)
if err != nil {
    return err
}
exists := false
for _, info := range infos {
    if info.Pkg.Name == pkgName {
        exists = true
        break
    }
}
if exists && !userConfirmedOverwrite {
    return errors.New("ask user: overwrite existing package?")
}

Type guard

func isPackageExistsErr(err error) bool {
    return errors.Is(err, model.ErrLocalBazaarPackageExists)
}

Try / catch

result, err := model.InstallLocalBazaarPackage(archivePath, frontend, false)
if err != nil {
    if errors.Is(err, model.ErrLocalBazaarPackageExists) {
        // prompt user; on confirm retry with overwrite=true
    }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18). Data as JSON: /api/errors/b82dc07fe782aab5. Report an issue: GitHub.