Describe the bug
A server-managed extraKnownMarketplaces entry is fetched successfully and parses correctly, but is never merged into the plugin/marketplace code path, so the marketplace is never registered. copilot plugin marketplace list shows only the two defaults, copilot plugin marketplace browse <name> reports "Marketplace not found", and the Copilot desktop app's Settings → Plugins pane shows only copilot-plugins and awesome-copilot.
The failure is completely silent — no error, no warning, no log line.
This appears distinct from #4039 and #4283, which both concern what happens after a managed marketplace/plugin is registered (sync-to-disk and enablement persistence). Here the marketplace is never registered at all, so no plugin from it is installable by any means.
Root cause
In app.js (1.0.80), the marketplace path self-fetches managed settings via XE():
async function XE(){
let t = await yft();
if (!t) return; // bails -> returns undefined
let e = await iP({selfFetch:!0, authInfo:{type:"user", host:t.host, login:t.login}});
...
}
async function A1(t,e,n){
let r = await Nfe(t),
s = Bg(e,n)?.extraKnownMarketplaces; // e undefined -> s undefined
return s ? {...r, extraKnownMarketplaces:{...s, ...r.extraKnownMarketplaces}} : r;
}
yft() resolves the account via authGetLastLoggedInUser(<configDir>/config.json). When the CLI has no local login record, it returns undefined, XE() bails, and A1() silently returns user settings only. B8() additionally wraps the call in .catch(()=>{}), so even a thrown error is swallowed.
This is reachable in normal use because the session path and the marketplace path resolve auth differently. When the CLI is driven by the desktop app (copilot --server --stdio), the app supplies auth to the session, and the session path fetches the policy fine — the same process logs:
[managedSettings] server policy received from https://github.com: bypassDisabled=false, keys=[enabledPlugins,extraKnownMarketplaces]
…while the marketplace path in that same process sees no account and skips the fetch entirely. So enterprise policy is demonstrably present and enforced (bypass-permissions restrictions apply), yet extraKnownMarketplaces is inert.
Affected version
copilot 1.0.80
GitHub Copilot desktop app 1.1.11
Steps to reproduce the behavior
- Publish a marketplace repo containing
.claude-plugin/marketplace.json.
- Configure enterprise managed settings with:
{
"extraKnownMarketplaces": {
"acme": { "source": { "source": "github", "repo": "ACME/agent-plugins" } }
},
"enabledPlugins": {}
}
- Confirm the server actually returns it:
$ gh api /copilot_internal/managed_settings
{"extraKnownMarketplaces":{"acme":{"source":{"source":"github","repo":"ACME/agent-plugins"}}},"enabledPlugins":{}}
- Confirm the client receives it (from the app-spawned CLI log in
~/.copilot/logs/):
[managedSettings] server policy received from https://github.com: bypassDisabled=false, keys=[enabledPlugins,extraKnownMarketplaces]
- List marketplaces:
$ copilot plugin marketplace list
Included with GitHub Copilot:
◆ copilot-plugins (GitHub: github/copilot-plugins)
◆ awesome-copilot (GitHub: github/awesome-copilot)
$ copilot plugin marketplace browse acme
Failed to browse marketplace: Error: Marketplace "acme" not found
The acme marketplace is absent, and absent from the desktop app's Plugins pane.
Expected behavior
A marketplace declared in server-managed extraKnownMarketplaces should be registered and appear in copilot plugin marketplace list and in the desktop app's Plugins pane, exactly as the equivalent user-scope setting does.
Failing that, the policy fetch being skipped for lack of an account should produce a visible warning rather than silently degrading to user settings.
Additional context
The setting itself is valid — every other layer handles it correctly. Isolating each layer:
The identical JSON works at user scope, which rules out the shape/discriminator:
$ COPILOT_HOME=/tmp/cptest copilot plugin marketplace list
Registered marketplaces:
• acme (GitHub: ACME/agent-plugins)
$ COPILOT_HOME=/tmp/cptest copilot plugin marketplace browse acme
Plugins in "acme":
• ... (all plugins resolve correctly)
The Rust resolver preserves the key from both layers. Calling the native module directly:
const h = require(".../prebuilds/darwin-arm64/runtime.node");
const j = JSON.stringify({
extraKnownMarketplaces: { acme: { source: { source: "github", repo: "ACME/agent-plugins" } } },
enabledPlugins: {}
});
h.managedSettingsResolveEffective(j, undefined); // server layer
h.managedSettingsResolveEffective(undefined, j); // device layer
Both return the key intact:
{"enabledPlugins":{},"extraKnownMarketplaces":{"acme":{"source":{"source":"github","repo":"ACME/agent-plugins"}}}}
The auth lookup is what fails. Against the real config:
await h.authGetLastLoggedInUser(os.homedir()+"/.copilot/config.json", spec);
// => undefined (NO LOGGED-IN USER)
There is no CLI-side login record: the desktop app stores its token in its own data.db (github_accounts.access_token), and there is no Keychain entry for the CLI. So yft() cannot resolve an account even though the app is fully authenticated and the session path fetches policy successfully in the same process.
Note on the device/MDM path. Policy delivered via the device file (/Library/Application Support/GitHubCopilot/managed-settings.json) does not go through yft(), so validating a config that way can pass while server-delivered policy fails. Conformance testing that uses only the MDM path will not catch this.
Environment
- Operating system: macOS 26.5.1
- CPU architecture: arm64
- Copilot CLI: 1.0.80 (engine spawned by the desktop app as
copilot --server --stdio)
- Copilot desktop app: 1.1.11
- Policy source: server-managed (enterprise), confirmed via
/copilot_internal/managed_settings
Describe the bug
A server-managed
extraKnownMarketplacesentry is fetched successfully and parses correctly, but is never merged into the plugin/marketplace code path, so the marketplace is never registered.copilot plugin marketplace listshows only the two defaults,copilot plugin marketplace browse <name>reports "Marketplace not found", and the Copilot desktop app's Settings → Plugins pane shows onlycopilot-pluginsandawesome-copilot.The failure is completely silent — no error, no warning, no log line.
This appears distinct from #4039 and #4283, which both concern what happens after a managed marketplace/plugin is registered (sync-to-disk and enablement persistence). Here the marketplace is never registered at all, so no plugin from it is installable by any means.
Root cause
In
app.js(1.0.80), the marketplace path self-fetches managed settings viaXE():yft()resolves the account viaauthGetLastLoggedInUser(<configDir>/config.json). When the CLI has no local login record, it returns undefined,XE()bails, andA1()silently returns user settings only.B8()additionally wraps the call in.catch(()=>{}), so even a thrown error is swallowed.This is reachable in normal use because the session path and the marketplace path resolve auth differently. When the CLI is driven by the desktop app (
copilot --server --stdio), the app supplies auth to the session, and the session path fetches the policy fine — the same process logs:…while the marketplace path in that same process sees no account and skips the fetch entirely. So enterprise policy is demonstrably present and enforced (bypass-permissions restrictions apply), yet
extraKnownMarketplacesis inert.Affected version
Steps to reproduce the behavior
.claude-plugin/marketplace.json.{ "extraKnownMarketplaces": { "acme": { "source": { "source": "github", "repo": "ACME/agent-plugins" } } }, "enabledPlugins": {} }~/.copilot/logs/):The
acmemarketplace is absent, and absent from the desktop app's Plugins pane.Expected behavior
A marketplace declared in server-managed
extraKnownMarketplacesshould be registered and appear incopilot plugin marketplace listand in the desktop app's Plugins pane, exactly as the equivalent user-scope setting does.Failing that, the policy fetch being skipped for lack of an account should produce a visible warning rather than silently degrading to user settings.
Additional context
The setting itself is valid — every other layer handles it correctly. Isolating each layer:
The identical JSON works at user scope, which rules out the shape/discriminator:
The Rust resolver preserves the key from both layers. Calling the native module directly:
Both return the key intact:
{"enabledPlugins":{},"extraKnownMarketplaces":{"acme":{"source":{"source":"github","repo":"ACME/agent-plugins"}}}}The auth lookup is what fails. Against the real config:
There is no CLI-side login record: the desktop app stores its token in its own
data.db(github_accounts.access_token), and there is no Keychain entry for the CLI. Soyft()cannot resolve an account even though the app is fully authenticated and the session path fetches policy successfully in the same process.Note on the device/MDM path. Policy delivered via the device file (
/Library/Application Support/GitHubCopilot/managed-settings.json) does not go throughyft(), so validating a config that way can pass while server-delivered policy fails. Conformance testing that uses only the MDM path will not catch this.Environment
copilot --server --stdio)/copilot_internal/managed_settings