github / github/app

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

Ouverte
#2,798 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub
Langage dominant
Aucune donnée de langage
Étoiles
2.1k
Forks
153
Métriques de merge des PR
Aucune PR mergée en 30 j

Description

# 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 |

Guide de contribution

Ouvrir le guide de contribution

Piste de recherche

Commencez par les points d’entrée RPC du marketplace de plugins, en particulier authenticate_plugin_marketplace, refresh_plugin_marketplaces et le chemin extraKnownMarketplaces de settings.json. Suivez ensuite la recherche de comptes du marketplace dans data.db, puis examinez le flux de suppression du cache pour les fichiers de pack Git en lecture seule sous Windows. La tâche est terminée lorsque les marketplaces privés peuvent être associés au compte prévu et que l’actualisation/l’installation du marketplace réussit sans erreur access-denied.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
git, github, json
Domaine
authentication, databases, desktop, devtools
Type d'issue
Bug
Difficulté
5/5
Temps estimé
Plus d'une semaine
Activité
Calme
Clarté
Plutôt claire
Accessibilité débutants
35/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.