Surface extensions blocked by the marketplace - #333859
Open
Michael Cummings (MSFT) (mcumming) wants to merge 1 commit into
Open
Surface extensions blocked by the marketplace#333859Michael Cummings (MSFT) (mcumming) wants to merge 1 commit into
Michael Cummings (MSFT) (mcumming) wants to merge 1 commit into
Conversation
Copilot started reviewing on behalf of
Michael Cummings (MSFT) (mcumming)
September 1, 2026 20:17
View session
The marketplace previously omitted extensions it would not serve, so a user searching for one saw an empty result and no reason for it. It now returns the record carrying a `blocked` flag instead, and this reads that flag and explains the block rather than letting the extension disappear. The flag comes at two granularities -- a whole extension, or a single version while the others remain available -- and both are read. A blocked version is skipped when selecting one to acquire, so selection falls through to a permitted version, in display paths as well as install paths: a version pinned out by policy is usually the newest, and showing it would strand an extension whose earlier versions are perfectly usable. The two granularities are not distinguished in the result, because a block only reaches a user once no version is permitted, and at that point the extension is unavailable to them whichever granularity produced it. Blocked extensions are still returned by the gallery, so enforcement lives at the decision points -- canInstall, auto-update, and the extensionDisallowInstall context key -- rather than by filtering them out of results. The block takes precedence over deprecation, and the message distinguishes an organization policy decision from a marketplace statement, since conflating the two either alarms users about a routine policy decision or understates a marketplace finding. Report the reason canInstall determined when refusing an install, rather than discarding it for a fixed platform-incompatibility message. That message was already wrong for an allow list denial, and adding a second cause to the same path would have made a blocked extension claim it was unavailable for the machine's platform, sending the user to look for one that could run it. The paths reaching this are the ones with no other signal: settings sync, the command line, and the URI handler. Only the flag and the origin are read. The contract also defines a reason, none of whose values the registry populates today: allow-list denials are the only blocks issued, and they carry origin policy. Origin is kept rather than collapsed to a boolean because attributing a marketplace block to the user's own organization would be misleading. Absent, unrecognized, and wrong-typed values degrade to the marketplace wording rather than failing the parse, which would have taken down the whole query rather than the one extension. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2c07f98f-e549-41c2-af5b-7f197c533c8c
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Installed extension models can retain stale blocking metadata and bypass the new UI and chat safeguards.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Balanced
Findings: 1
New issues introduced by this change (4)
| Severity | Finding |
|---|---|
src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts — blockingInfo can remain stale for installed extensions. queryGallery/getExtensions return an… |
|
src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts — This multi-line comment restates the branch and error construction. A short note captures the… |
|
src/vs/platform/extensionManagement/common/extensionGalleryService.ts — This long JSDoc duplicates implementation details and caller behavior, so it is likely to drift as… |
|
src/vs/platform/extensionManagement/common/extensionGalleryService.ts — This multi-line inline comment narrates the surrounding selection logic and includes a fallback… |
What changed in this PR
Adds client support for marketplace extension-blocking metadata and enforces it across installation, updates, UI, and chat search.
Changes:
- Parses extension- and version-level blocking metadata.
- Prevents blocked extension acquisition and surfaces origin-specific messages.
- Adds contract parsing and message tests.
| File | Description |
|---|---|
searchExtensionsTool.ts |
Excludes blocked extensions from chat results. |
extensions.ts |
Exposes blocking metadata in the workbench model. |
extensionsWorkbenchService.ts |
Applies blocking to updates and installation. |
extensionsActions.ts |
Disables actions and displays blocked status. |
extensionManagement.test.ts |
Tests origin-specific messages. |
extensionGalleryService.test.ts |
Tests blocking contract parsing. |
extensionManagementUtil.ts |
Builds localized blocking messages. |
extensionManagement.ts |
Defines public blocking types. |
extensionGalleryService.ts |
Parses blocking data and filters versions. |
abstractExtensionManagementService.ts |
Enforces blocks and preserves failure reasons. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+238
to
+240
| get blockingInfo(): IExtensionBlockingInfo | undefined { | ||
| return this.gallery?.blockingInfo; | ||
| } |
Comment on lines
+729
to
+731
| // Report the reason canInstall determined - a policy block or an allow list | ||
| // denial is not a platform incompatibility, and saying so sends the user | ||
| // looking for a machine that can run it. |
Comment on lines
+387
to
+397
| /** | ||
| * Reads the blocked state of an extension at a given version. | ||
| * | ||
| * At either granularity the flag and the object are two representations of one fact, so either | ||
| * alone still means blocked. A producer emitting one without the other is defective, and failing | ||
| * safe is the correct response. | ||
| * | ||
| * The two are not distinguished in the result. A blocked version is skipped when selecting one | ||
| * to acquire, so it only reaches a user when no version is permitted - and at that point the | ||
| * extension is unavailable to them, whichever granularity produced it. | ||
| */ |
Comment on lines
+1061
to
+1065
| // A version the registry has blocked is skipped so that selection falls through to a | ||
| // permitted one, in display paths as well as install paths: showing a blocked latest | ||
| // version would strand an extension whose earlier versions are perfectly usable. When | ||
| // no version is permitted the caller's fallback still surfaces the extension, so it | ||
| // explains itself rather than vanishing. |
Michael Cummings (MSFT) (mcumming)
force-pushed
the
dev/mcumming/blocked-extension-reason
branch
from
September 1, 2026 20:34
2020982 to
b980ceb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


The Private Marketplace evaluates an administrative allow/deny policy on every extension query, and until now a denial reached the client only as an absence: the extension was simply not in the results, or the install failed, and the developer had nothing to read. That becomes an internal support ticket, fielded by the same administrators who made the decision.
The registry now returns the record carrying a
blockedflag instead of omitting it. This reads that flag and explains the block.Implements the client half of the Extension Blocking wire contract (spec in review at
VSMarketplace.SpecsPR 951559). No behaviour changes unless a gallery sets the flag, which no deployed marketplace does yet.What it does
Blocked extensions are still returned by the gallery, so enforcement lives at the decision points rather than by filtering them out of results:
canInstall, on both the platform and workbench servicesextensionDisallowInstallcontext keyExtensionStatusAction, where the block takes precedence over deprecationsearchExtensionsTool, alongside the existing deprecated and malicious filtersThe message distinguishes an organization policy decision from a marketplace statement. Conflating those either alarms users about a routine policy decision or understates a marketplace finding.
Two granularities
The flag applies either to a whole extension or to a single version while the others remain available. Both are read, but they are not distinguished in the result.
A blocked version is skipped when selecting a version to acquire, in display paths as well as install paths. A version pinned out by policy is usually the newest one, so selecting it would strand an extension whose earlier versions are perfectly usable. Because of that, a block only reaches a user once no version is permitted at all - and at that point the extension is unavailable to them whichever granularity caused it, so a version-specific message would only send them looking for an alternative that does not exist.
Install failures now report the actual cause
canInstallcomputes a precise reason, butcheckAndGetCompatibleVersiondiscarded it and threw a fixed platform-incompatibility message. That was already wrong for an allow list denial, and adding blocked to the same path would have made a blocked extension claim it was unavailable for the machine's platform.It now throws the message
canInstallreturned. The error code is deliberately unchanged: both consumers ofIncompatibleTargetPlatform- settings sync's skip-and-do-not-retry, and the install error dialog - already do the right thing for a blocked extension.This only affects paths that bypass the disabled install button: settings sync,
--install-extension, thevscode:extension/idURI handler, and workspace recommendations. Those are the ones with no other signal.Scope
Only the flag and the origin are read. The contract also defines a
reason, none of whose values the registry populates today - allow list denials are the only blocks issued, and they carryorigin: policy. Origin is kept rather than collapsed to a boolean so that a future marketplace-originated block is not attributed to the user's own organization.Absent, unrecognized, and wrong-typed values degrade to the more severe reading rather than failing the parse, which would have taken down the whole query rather than the one extension.
Not included, and worth a separate discussion: an extension already installed when a block arrives is gated from updating but is not disabled or reported. The sibling states handle that through
EnablementState.DisabledByAllowlistand the extensions notification, which would need the blocked state persisted ontoILocalExtension.Testing
Unit tests cover the contract's consumer conformance rules: flag without object, object without flag, whole-token flag matching against unknown tokens, degradation of unrecognized, missing, null, and wrong-typed origins, extension blocks outranking version blocks, and the message wording per origin.
Not yet exercised end to end - no deployed marketplace emits the contract, so that needs a stubbed gallery response.