# GitHub Copilot app: private plugin marketplaces are unusable, and marketpla...
- 主要言語
- 言語のデータがありません
- スター
- 2.1k
- フォーク
- 153
- PR マージ指標
- 30日以内にマージされた PR はありません
説明
# GitHub Copilot app: private plugin marketplaces are unusable, and marketplace refresh fails on read-only git files
**Component:** GitHub Copilot desktop app (`github.exe`) — plugin store / marketplace fetch
**Version:** app build with Copilot CLI 1.0.79, bundled git 2.53.0-3, bundled gh 2.96.0 · Windows
**Reported symptom:** "access denied" when installing or updating plugins
Two independent defects produce that one symptom. They are unrelated and can be fixed separately.
---
## Bug 1 — A private marketplace is always fetched with the *default* account
### Summary
Marketplaces declared in `~/.copilot/settings.json` under `extraKnownMarketplaces` never get a row in
the app's `plugin_marketplace_accounts` table, so the marketplace clone silently falls back to the
**default** GitHub account. If that account cannot see the repository, the marketplace can never be
installed or updated, and there is no way for the user to fix it.
### Environment
Two GitHub accounts are signed in:
| account | `is_default` | can read `agency-microsoft/.github-private` |
| --- | --- | --- |
| `AsafMah` | 1 | no — HTTP 404 |
| `asafmahlev_microsoft` | 0 | yes — `private=true` |
The marketplace is declared in `~/.copilot/settings.json`:
```json
"extraKnownMarketplaces": {
"agency": { "source": { "source": "github", "repo": "agency-microsoft/.github-private" } }
}
```
### Evidence
Installing any plugin from that marketplace fails. From the app log:
```
09:56:29.281 received message kind="install_plugin" text={"type":"install_plugin","source":"a11y@agency"}
09:56:30.098 github_app::git::trampoline: trampoline accepted connection
09:56:30.099 github_app::git::trampoline: trampoline credential helper invoked verb="get"
09:56:30.099 github_app::git::trampoline: trampoline credential helper resolved token host="github.com"
09:56:30.647 ERROR ... failed to install plugin error=RPC error -32603: Request plugins.install failed
with message: Failed to fetch marketplace: Failed to fetch GitHub marketplace
agency-microsoft/.github-private: Command failed: git clone --depth 1 --progress
https://github.com/agency-microsoft/.github-private.git
C:\Users\...\AppData\Local\copilot\marketplaces\agency-microsoft-.github-private
Cloning into '...'...
remote: Repository not found.
fatal: Authentication failed for 'https://github.com/agency-microsoft/.github-private.git/'
```
The trampoline resolves a token *by host only* and returns the default account's token, which 404s on
that private repo.
### The mechanism already exists but is never populated
`data.db` contains exactly the right table, and the app queries it:
```sql
CREATE TABLE plugin_marketplace_accounts (
marketplace_name TEXT PRIMARY KEY NOT NULL,
account_id TEXT NOT NULL,
FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE
);
SELECT account_id FROM plugin_marketplace_accounts WHERE marketplace_name = ?1
INSERT INTO plugin_marketplace_accounts (marketplace_name, account_id) VALUES (?1, ?2)
ON CONFLICT(marketplace_name) DO UPDATE SET account_id = excluded.account_id
```
The RPC surface includes `add_plugin_marketplace`, **`authenticate_plugin_marketplace`**,
`remove_plugin_marketplace`, `browse_plugin_marketplace`, `refresh_plugin_marketplaces`.
The feature is enabled for this user — telemetry reports
`"features.private_marketplace_authentication":"true"`.
Despite that:
- `SELECT * FROM plugin_marketplace_accounts` returns **zero rows**.
- No `add_plugin_marketplace` or `authenticate_plugin_marketplace` call appears anywhere in the
retained app logs — the marketplace was declared directly in `settings.json` (by the Agency
installer), so it never passed through the flow that records an account.
- The plugin store UI offers **no** authenticate / sign-in affordance for this marketplace.
### Impact
Any user whose default Copilot account differs from the account that can read a private marketplace
is permanently blocked from installing or updating its plugins, with a misleading "access denied"
message and no path to resolve it. Changing the default account is not a workaround — projects are
bound to explicit accounts and other repositories depend on the current default.
### Repro
1. Sign in with two GitHub accounts; make the one *without* access to a private repo the default.
2. Add that private repo as a marketplace via `~/.copilot/settings.json` `extraKnownMarketplaces`
(i.e. the CLI path, not the app's "add marketplace" flow).
3. Try to install any plugin from it → `Repository not found` / `Authentication failed`.
### Suggested fix
1. Surface `authenticate_plugin_marketplace` in the plugin store for **any** marketplace, including
those declared in `settings.json` — ideally offered automatically when a fetch fails with 404/403
and more than one account is signed in.
2. When resolving a marketplace fetch, consult `plugin_marketplace_accounts` and fall back to the
default account only when no binding exists (this may already be the intent; the row is simply
never created for CLI-declared marketplaces).
3. Report the underlying auth failure distinctly from a filesystem permission error — see Bug 2, which
surfaces under the identical "access denied" wording.
### Note
Inserting the row manually while the app was running had no effect — the same install still resolved
the default account. Whether the app reads this table only at startup was not verified (no restart
was performed).
---
## Bug 2 — Marketplace refresh fails with `Access is denied. (os error 5)` on read-only git pack files
### Summary
Installing a plugin triggers a marketplace refresh that recursively deletes the cached clone. Git
marks pack files (`.pack`, `.idx`, `.rev`) **read-only**, and a recursive delete that does not clear
the read-only attribute fails on Windows with `ERROR_ACCESS_DENIED (5)`. This affects **every**
marketplace, public ones included, and is unrelated to authentication.
### Evidence
```
09:05:07.633 received message kind="install_plugin"
text={"type":"install_plugin","source":"csharp-dotnet-development@awesome-copilot"}
09:05:15.436 ERROR ... failed to install plugin error=RPC error -32603: Request plugins.install failed
with message: Failed to install plugin: Access is denied. (os error 5)
source=csharp-dotnet-development@awesome-copilot
```
The marketplace cache contained read-only files, all of them git pack data:
```
marketplaces\github-awesome-copilot\.git\objects\pack\pack-4f4ed23f….idx (read-only)
marketplaces\github-awesome-copilot\.git\objects\pack\pack-4f4ed23f….pack (read-only)
marketplaces\github-awesome-copilot\.git\objects\pack\pack-4f4ed23f….rev (read-only)
marketplaces\github-awesome-copilot\.git\objects\pack\pack-803651e3….idx (read-only)
marketplaces\github-awesome-copilot\.git\objects\pack\pack-803651e3….pack (read-only)
marketplaces\github-awesome-copilot\.git\objects\pack\pack-803651e3….rev (read-only)
```
71 read-only files existed across all marketplace caches.
### Mechanism confirmed in isolation
```python
sub = /objects/pack ; write /objects/pack/x.pack ; os.chmod(f, stat.S_IREAD)
shutil.rmtree() # same syscall pattern as Rust std::fs::remove_dir_all
-> PermissionError winerror 5 "Access is denied"
os.chmod(f, stat.S_IWRITE) ; shutil.rmtree() -> succeeds
```
Clearing the read-only attribute on those 71 files made the deletion succeed.
### Impact
Plugin install/update breaks for any marketplace whose cache has been fetched at least once — git
creates read-only packs on clone and on every subsequent fetch, so this recurs continuously and
resolves itself only by chance. The error message ("access denied") strongly implies a permissions or
authentication problem, sending users down the wrong path entirely.
### Repro
1. Let the app clone any marketplace (creates read-only `.pack`/`.idx`/`.rev` files).
2. Install a plugin from it, triggering the refresh/delete.
3. → `Failed to install plugin: Access is denied. (os error 5)`.
### Suggested fix
Clear the read-only attribute before recursive deletion on Windows — the standard remedy is a
`remove_dir_all` that retries after `set_readonly(false)` on `ERROR_ACCESS_DENIED` (e.g. the
`remove_dir_all` crate), rather than `std::fs::remove_dir_all` directly. Apply it everywhere the app
deletes a git working copy, not just marketplace caches.
---
## Related observation (not investigated)
The app log shows a repeating ~12 s cycle in `resources_subscription_loop`:
```
github_app::git::remote: Both local and token auth failed label=…
github_app::git: git command exited with non-zero status
```
This looks like a background repo poll retrying failed auth indefinitely. Possibly the same
default-account root cause as Bug 1, but it was not investigated.
---
| Field | Value |
| --- | --- |
| App version | 1.1.7 |
| OS | Windows 10.0.26200 |
| Theme | GitHub |
| Path | /chat |
| Tenure | Week 6 |
コントリビューションガイド
調査の方向性
プラグインマーケットプレイスの RPC エントリポイントから始めます。特に authenticate_plugin_marketplace、refresh_plugin_marketplaces、および settings.json の extraKnownMarketplaces パスを確認してください。次に、data.db でのマーケットプレイスアカウントの検索を追跡し、その後、Windows の読み取り専用 Git pack ファイルに対するキャッシュ削除フローを調べます。プライベートマーケットプレイスを意図したアカウントに紐付けられ、マーケットプレイスの更新/インストールが access-denied エラーなしで成功すれば完了です。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- git, github, json
- 領域
- authentication, databases, desktop, devtools
- issue の種類
- バグ
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 活発さ
- 静か
- 明瞭さ
- おおむね明確
- 初心者へのやさしさ
- 35/100