fix: local plugin upgrade support + version persistence#31
Conversation
The TUI's upgrade handler previously called 'copilot plugin update' for all plugins, which fails for local (git-backed) sources since the CLI doesn't know how to resolve them in a marketplace. Now, local plugins are upgraded via git fetch + checkout of the target tag. A new upgrade_local_plugin() function handles the git operations, and the tab dispatches to either local or marketplace upgrade paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After a successful local plugin upgrade (git checkout), write the new version back to config.json's installedPlugins[].version field. Without this, the reported version stays stale indefinitely for plugins that lack a package.json (config-only repos). Also rename MCP server source label from 'config' to 'user' for servers defined directly in mcp-config.json (not bundled by a plugin). Addresses upstream bug: github/copilot-cli#3129 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Align Skills and Agents dataclass defaults to 'user' for items created directly by the user (not bundled by a plugin). MCP Servers already used 'user'. Plugins keep 'local' since that's the upstream Copilot CLI marketplace field value. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Use write_json() for atomic config.json writes in set_plugin_version() (temp file + rename, with .bak rotation) - Check set_plugin_version() return value and show a warning notification if the git upgrade succeeded but config persistence failed Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds end-to-end support for upgrading local (git-backed) plugins in the TUI (via git fetch --tags + git checkout <tag>) and persists upgraded versions back into ~/.copilot/config.json. Also standardizes the “source” label for user-defined MCP servers (and related user-scanned items) to "user".
Changes:
- Introduces
upgrade_local_plugin()and wires the Plugins tab upgrade action to dispatch between local (git checkout) vs marketplace (copilot plugin update) upgrades. - Adds
set_plugin_version()to persist upgraded versions intoconfig.json, plus test coverage. - Renames MCP server source label for non-plugin servers from
"config"to"user"(and aligns skills/agents user source defaults).
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/test_plugin_upgrades.py | Adds unit tests covering upgrade_local_plugin() success/failure paths. |
| tests/test_data_skills.py | Updates expected source label for user skills to "user". |
| tests/test_data_plugins.py | Adds tests for set_plugin_version() behavior (including JSONC handling). |
| tests/test_data_mcp_servers.py | Updates expected MCP server default source label to "user". |
| src/copilotsetup/tabs/plugins.py | Adds local-vs-marketplace upgrade dispatch and version persistence after local upgrades. |
| src/copilotsetup/plugin_upgrades.py | Implements upgrade_local_plugin() using git fetch/checkout. |
| src/copilotsetup/data/skills.py | Changes user skill default source and labeling from "local" → "user". |
| src/copilotsetup/data/plugins.py | Adds upgrade_version to PluginInfo and implements set_plugin_version(). |
| src/copilotsetup/data/mcp_servers.py | Changes default source for non-plugin servers from "config" → "user". |
| src/copilotsetup/data/agents.py | Changes default source for user agents to "user". |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
git fetch --tags is now best-effort — if it fails (no remote, network down), the upgrade still attempts git checkout since the tag may exist locally. This matches check_plugin()'s behavior and handles plugins without an origin remote. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed review comment on Fixed in 2f9c0ea -- fetch is now best-effort (no explicit remote, non-fatal on failure). If the tag already exists locally, checkout proceeds regardless. Split test into two cases: fetch-fails-checkout-succeeds and fetch-fails-checkout-fails. |
… installs When a plugin is installed from a local path that is a git checkout on a branch (active dev work), the TUI's upgrade detector previously fell back to ``git describe --tags --abbrev=0`` to find the nearest ancestor tag and compared that to the highest tag on origin. This produced a misleading ``↑ vX.Y.Z`` arrow even though pressing ``[u]`` could not actually upgrade the plugin -- ``copilot plugin update`` only does ``git pull`` on the current branch, so the user's dev branch never advances to the release tag. Now we distinguish three install states: 1. Tag-pinned (detached HEAD on an exact tag): unchanged. Show ``↑ vX.Y.Z`` if origin has a higher tag; ``[u]`` works via the CLI. 2. Dev checkout (HEAD on a branch -- including a branch whose tip happens to coincide with a tag): NEW. Show ``dev: <branch>`` in the Upgrade column instead of an arrow. The detail pane explains the local install state, surfaces the latest origin tag for context, and shows the suggested git command to pin to a release. Pressing ``[u]`` short- circuits with a helpful message pointing at the source repo -- it does not call the CLI. 3. Other (no tags, missing path, not git): unchanged. Branch state is authoritative. We deliberately ignore exact-tag detection when HEAD is on a branch, because ``copilot plugin update`` will ``git pull`` the branch rather than ``git checkout`` the tag, so we cannot promise tag-based upgrades will land. Also clears stale ``upgrade_available``/``upgrade_version`` in the no- upgrade fallback of ``_apply_upgrades`` so a row that flips from provisional-upgradable to fresh-no-upgrade does not still trigger the CLI when ``[u]`` is pressed. This is purely informational. We do NOT re-introduce any git-checkout logic in the upgrade handler -- that was the path PR #31 took and PR #32 deliberately reverted, after upstream confirmed ``copilot plugin update`` handles fresh-tag local installs correctly. The bug here is in our display heuristics, not in the upgrade action. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… installs When a plugin is installed from a local path that is a git checkout on a branch (active dev work), the TUI's upgrade detector previously fell back to ``git describe --tags --abbrev=0`` to find the nearest ancestor tag and compared that to the highest tag on origin. This produced a misleading ``↑ vX.Y.Z`` arrow even though pressing ``[u]`` could not actually upgrade the plugin -- ``copilot plugin update`` only does ``git pull`` on the current branch, so the user's dev branch never advances to the release tag. Now we distinguish three install states: 1. Tag-pinned (detached HEAD on an exact tag): unchanged. Show ``↑ vX.Y.Z`` if origin has a higher tag; ``[u]`` works via the CLI. 2. Dev checkout (HEAD on a branch -- including a branch whose tip happens to coincide with a tag): NEW. Show ``dev: <branch>`` in the Upgrade column instead of an arrow. The detail pane explains the local install state, surfaces the latest origin tag for context, and shows the suggested git command to pin to a release. Pressing ``[u]`` short- circuits with a helpful message pointing at the source repo -- it does not call the CLI. 3. Other (no tags, missing path, not git): unchanged. Branch state is authoritative. We deliberately ignore exact-tag detection when HEAD is on a branch, because ``copilot plugin update`` will ``git pull`` the branch rather than ``git checkout`` the tag, so we cannot promise tag-based upgrades will land. Also clears stale ``upgrade_available``/``upgrade_version`` in the no- upgrade fallback of ``_apply_upgrades`` so a row that flips from provisional-upgradable to fresh-no-upgrade does not still trigger the CLI when ``[u]`` is pressed. This is purely informational. We do NOT re-introduce any git-checkout logic in the upgrade handler -- that was the path PR #31 took and PR #32 deliberately reverted, after upstream confirmed ``copilot plugin update`` handles fresh-tag local installs correctly. The bug here is in our display heuristics, not in the upgrade action. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Adds support for upgrading "local" (git-backed) plugins in the TUI and fixes the version persistence bug.
Changes
Local plugin upgrade support:
upgrade_local_plugin(install_path, target_version)function — runsgit fetch --tags+git checkout <tag>handle_upgradenow dispatches: local plugins use git checkout, marketplace plugins usecopilot plugin updateupgrade_versionfield toPluginInfodataclassVersion persistence after upgrade:
set_plugin_version(name, version)function — writes the new version back toconfig.jsoncopilot plugin list/ TUI shows the correct versionMCP server source label:
mcp-config.json(not bundled by a plugin)Testing
upgrade_local_plugin, 4 forset_plugin_version)