Installing a plugin while its removal is still pending deletes the new copy

Zotero 10.0.2, Linux.

If you remove a plugin and install it again without restarting Zotero in
between, the copy you just installed is deleted a few seconds later. The install
reports success, the plugin loads and runs, and then it disappears — file,
extensions.json entry, and its whole preference branch.

Steps:

1. Tools -> Plugins -> Remove, on any plugin.
2. Without restarting, install the plugin again from a file.
3. Watch it appear, work, and then vanish after a few seconds.

What I measured. Reproduced four times in one hour; three of them watched from
outside the process at 50 ms resolution, which gives the same shape each time:

- Remove creates extensions/staged// and extensions.json still names
the add-on. That is uninstallAddon's aForcePending branch.
- The install rewrites the .xpi, empties and deletes the staged entry, and
extensions.json returns to naming the add-on. The install genuinely succeeds
and the pending marker is genuinely cleared.
- 3.3 to 4.7 seconds later the .xpi is deleted, extensions.json loses the entry,
and half a second after that the add-on's preference branch is cleared.

With extensions.logging.enabled, the add-on manager shows it. Times are its
own epoch-ms stamps, offset from the first install:

0.000 Starting install of from file:///.../.xpi
0.031 Calling bootstrap method install, then startup
14.258 Disabling XPIState / Calling bootstrap method shutdown <- Remove
17.801 Starting install of <id> from file:///.../.xpi <- reinstall
17.815 Install of ....xpi completed.
17.818 Calling bootstrap method update, then startup <- new copy live
19.271 Calling bootstrap method shutdown
19.327 Calling bootstrap method uninstall
19.328 uninstallAddon: flushing jar cache .../<addon>.xpi <- file destroyed

So the queued removal finalises through the non-pending branch —
bootstrap.uninstall() then installer.uninstallAddon(id) — against the copy that
had replaced it. It appears to complete by add-on id without rechecking that a
newer install now holds that id.

Two things that may narrow it down. It does not need a restart: all four
occurrences happened in one session. And I could not reproduce it by driving
AddonManager directly — getInstallForFile plus installAddonFromAOMWithOptions
after addon.uninstall(true), with the pending state confirmed present
(pendingUninstall true, extensions.pendingOperations true, the staged directory
on disk), survives both the wait and a restart. That path cancels the pending
operation correctly, which suggests whatever finalises the removal is reached
through the Plugins window rather than through the API.

Why it is worth fixing rather than documenting: the failure is silent and the
plugin looks installed while it happens, so the natural reading is that the
plugin removed itself.

The workaround, for anyone who finds this thread: remove, quit Zotero, relaunch,
then install.
Sign In or Register to comment.