github / github/app

# GitHub Copilot app: private plugin marketplaces are unusable, and marketpla...

未关闭
#2,798 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
主要语言
没有语言数据
星标
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
预计耗时
一周以上
活跃度
冷清
描述清晰度
基本清楚
新手友好度
35/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。