Skip to content

fix: local plugin upgrade support + version persistence#31

Merged
ericchansen merged 5 commits intomainfrom
fix/local-plugin-upgrade
May 5, 2026
Merged

fix: local plugin upgrade support + version persistence#31
ericchansen merged 5 commits intomainfrom
fix/local-plugin-upgrade

Conversation

@ericchansen
Copy link
Copy Markdown
Owner

Summary

Adds support for upgrading "local" (git-backed) plugins in the TUI and fixes the version persistence bug.

Changes

Local plugin upgrade support:

  • New upgrade_local_plugin(install_path, target_version) function — runs git fetch --tags + git checkout <tag>
  • handle_upgrade now dispatches: local plugins use git checkout, marketplace plugins use copilot plugin update
  • Added upgrade_version field to PluginInfo dataclass

Version persistence after upgrade:

MCP server source label:

  • Renamed source label from "config" to "user" for MCP servers defined directly in mcp-config.json (not bundled by a plugin)

Testing

  • 8 new tests (4 for upgrade_local_plugin, 4 for set_plugin_version)
  • All 261 tests pass
  • Lint + format clean

ericchansen and others added 3 commits May 5, 2026 11:53
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>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 into config.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.

Comment thread src/copilotsetup/plugin_upgrades.py Outdated
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>
@ericchansen
Copy link
Copy Markdown
Owner Author

Addressed review comment on upgrade_local_plugin() hard-coding origin:

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.

@ericchansen ericchansen merged commit 84a694b into main May 5, 2026
8 checks passed
@ericchansen ericchansen deleted the fix/local-plugin-upgrade branch May 5, 2026 20:02
ericchansen added a commit that referenced this pull request May 6, 2026
… 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>
ericchansen added a commit that referenced this pull request May 6, 2026
… 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants