Skip to content

fix(plugins): refuse a URL install over a Git-installed plugin before downloading - #3091

Merged
matt2e merged 4 commits into
mainfrom
plugins-1
Nov 12, 2026
Merged

matt2e merged 4 commits into
mainfrom
plugins-1

Conversation

@matt2e

@matt2e matt2e commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Problem

A buzz://plugin-install?id=…&url=…&sha256=… link, or a plugin's requestInstall with a URL source, naming a plugin already installed from Git, a folder or an unknown source downloaded the artifact and showed an Update review with an enabled Update button. The refusal only came when the owner clicked Update, and Try again then looped back into the same download and refusal. Nothing in the dialog linked to the plugin that was actually installed. Introduced with #2910.

What changes

  • UrlPluginInstallReview computes a refusal from the live catalog at render and passes it to ManagedPluginReview. A refused review skips the download and shows a compact "Already installed" dialog with Close and Open plugin instead of Update or Try again.
  • Open plugin shows the plugin's details in Settings → Manage plugins through the shared openInstalledPlugin handler. The shared closePluginImport keeps those details open when the link visit's dismissal lands afterwards.
  • A plugin's refused request resolves cancelled. Its dialog reads "Requested by . …", matching the Install and Enable reviews.
  • ManagedPluginReview.onInstalled reports installed or updated from the catalog it checked right before installing, so removing the conflicting plugin while the dialog is open and then installing settles the request correctly.
  • docs/plugin-architecture.md and docs/buzz-help.md describe the refusal and Open plugin.

Unchanged

  • URL-installed plugins still get the Update review.
  • A failed download still offers Try again.
  • AgentPluginRequests' click-time recheck is untouched.

Verification

  • pnpm vitest run src/app/ManagedPlugins.test.tsx src/app/Settings.test.tsx src/app/PluginImport.test.tsx src/app/AgentPluginRequests.test.tsx src/plugins/installer.test.ts: 269 passed.
  • pnpm typecheck, Biome and pnpm design:check clean.
  • Chromium tests/browser/plugin-import.spec.mjs: 16/16.
  • Not run in WebKit or in the native app.

Before / after

Before: the link downloads the artifact, shows an Update review, refuses on click, and Try again loops. After: an immediate "Already installed" dialog with Close and Open plugin, and Open plugin lands on the plugin's details in Manage plugins.

Side by side, same journey in Chromium against the native-IPC fixture: install Artifact from Git, then deliver a buzz://plugin-install link for the same plugin. Left is main at 58116b8, right is this branch at e6c4ba7.

before-after-side-by-side.mp4

After: the compact "Already installed" dialog shown as the link arrives, offering only Close and Open plugin

Related

#3024 touches only PluginImport.tsx and is complementary.

🤖 Generated with Claude Code

@matt2e
matt2e marked this pull request as ready for review November 9, 2026 14:56
@matt2e
matt2e requested a review from a team as a code owner November 9, 2026 14:56

@kalvinnchau kalvinnchau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 One focus regression found.

Comment thread src/app/Settings.tsx Resolved
matt2e added a commit that referenced this pull request Nov 9, 2026
Open plugin on the "Already installed" dialog unmounted the dialog in the
same commit that showed the plugin's details. Base UI resolves an explicit
`finalFocus` in a layout cleanup, so the target ran before the details and
their Close button existed and fell back to the catalog's Add button, while
the Git path's Open in Buzz lands on Close plugin details.

The refusal dialog now closes with `open={false}` and unmounts from
`onOpenChangeComplete`, as the Git review does. The details mount and focus
their Close button first; the shared finalFocus guard then sees focus has
moved and leaves it there. The link's dismissal and a plugin's request still
settle once the dialog has closed. The Settings regression test asserts the
focus; a browser probe of the link flow passed in Chromium and WebKit with
this change and failed on both without it.

Addresses kalvinnchau's review of #3091.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
matt2e and others added 4 commits November 10, 2026 14:30
… downloading

A `buzz://plugin-install?id=…&url=…&sha256=…` link, or a plugin's
`requestInstall` with a URL source, naming a plugin already installed from
Git, a folder or an unknown source was only refused when the owner clicked
Update, after the artifact had been downloaded and an Update review shown.

`ManagedPluginReview` now takes a `refusal`, computed at render by
`UrlPluginInstallReview` from the live catalog. A refused review skips the
download and shows a compact "Already installed" dialog with Close and
Open plugin instead of Update or Try again. Open plugin shows the plugin's
details in Manage plugins through the shared `openInstalledPlugin` handler,
and the shared `closePluginImport` keeps those details when the link
visit's dismissal lands later. A plugin's request resolves `cancelled`.

URL-installed plugins still get the Update review, a failed download still
offers Try again, and agent requests' click-time recheck is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
…name its requester

Follow-ups to e6c4ba7, which refused a URL install over a Git- or
folder-installed plugin before downloading it.

`UrlPluginInstallReview` froze `updating` at mount, so removing the
conflicting plugin while the "Already installed" dialog was open let the
review download and install, then settle the plugin's request as `updated`
although a fresh install had happened. `ManagedPluginReview.onInstalled`
now reports `installed` or `updated` from the catalog it checked right
before installing, and the review passes that outcome through to `settle`;
`entry.state` follows the live catalog the same way.

A plugin's refused request now reads "Requested by <plugin>. …", matching
the Install and Enable reviews. A link's refusal renders exactly as before.

The help guide gains a sentence on the refusal and its Open plugin.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
The bullet 670dc90 added to the help guide said a `buzz://plugin-install`
link or URL install request is refused "when that plugin is already
installed", which can be read as the requesting plugin. It now says "when a
plugin with that ID is already installed from Git or a folder". Wording
suggestion from the review of that commit; no behavior change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Open plugin on the "Already installed" dialog unmounted the dialog in the
same commit that showed the plugin's details. Base UI resolves an explicit
`finalFocus` in a layout cleanup, so the target ran before the details and
their Close button existed and fell back to the catalog's Add button, while
the Git path's Open in Buzz lands on Close plugin details.

The refusal dialog now closes with `open={false}` and unmounts from
`onOpenChangeComplete`, as the Git review does. The details mount and focus
their Close button first; the shared finalFocus guard then sees focus has
moved and leaves it there. The link's dismissal and a plugin's request still
settle once the dialog has closed. The Settings regression test asserts the
focus; a browser probe of the link flow passed in Chromium and WebKit with
this change and failed on both without it.

Addresses kalvinnchau's review of #3091.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>

@kazemek21 kazemek21 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kyle reviewed the PR and the Nexus review-tracker notes and approved with no comments.

Posted by Codex (gpt-6.1-sol) on behalf of kylez

@matt2e
matt2e merged commit aea49ec into main Nov 12, 2026
64 checks passed
@matt2e
matt2e deleted the plugins-1 branch November 12, 2026 05:33
joahg added a commit that referenced this pull request Nov 12, 2026
Resolve against #3091: a URL install still updates a Git-installed plugin
in place after review, and only folder or unknown sources get the
pre-download "Already installed" refusal. Tests and docs that used a Git
install to trigger the refusal now use a folder install.

Refs AGNTOPS-791

Co-authored-by: Hertz <c6741012416ef5c49a865db79682113385c5fb998e8c890234fb442b59e22748@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
joahg added a commit that referenced this pull request Nov 12, 2026
**Bug:** Some plugins were installed from Git, but their newer versions
are now offered as downloads. Clicking **Update** or **Reinstall** fails
because Buzz treats this as a different install source. Users have to
delete the plugin and install it again. If the plugin is Marketplace
itself, deleting it also removes the page used to reinstall it.

**Cause:** The update screen blocks this switch, even though the
installer can already handle it.

**Fix:** Let users review the change and click **Update**. Buzz replaces
the plugin without deleting it first and remembers the download URL for
future updates. It keeps the plugin enabled or disabled as before, and
the same publisher and signature checks still apply.

| Before | After |
| --- | --- |
| ![Before: confirming the URL update refuses to replace the Git
installation](https://github.com/user-attachments/assets/947a698f-bf73-4926-8bfe-0c59e72a38fd)
| ![After: the update review explains the migration to a packaged URL
download](https://github.com/user-attachments/assets/c7950628-4858-425e-8090-1369d8881834)
|

The screenshots use an example plugin. **Before:** the update fails.
**After:** users can review the source change and continue.

**Folder and unknown sources:** A URL install over a plugin installed
from a folder or an unknown source is still refused before anything
downloads, with **Open plugin** to remove it first (#3091). Only Git
installations update in place.

**Validation:** Besides the unit and browser specs, the native installer
path was exercised against a copy of a real profile whose Marketplace
was installed from Git: installing the published Marketplace artifact
from its URL replaced the Git source with the URL, kept the plugin
enabled, left every other plugin unchanged, and **Roll back** restored
the Git build and source.

Refs AGNTOPS-791

buzz-review-completed

---------

Signed-off-by: Helen Tian <htian@squareup.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Joah Gerstenberg <joah@squareup.com>
Co-authored-by: Hertz <c6741012416ef5c49a865db79682113385c5fb998e8c890234fb442b59e22748@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants