OpenHands / OpenHands/software-agent-sdk

InstallationManager.update() silently drops pinned refs (should reconcile, not force ref=None)

Open Beginner friendly
#4,363 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug plugins priority:medium release-note-required sdk skills Stale
Dominant language
Python
Stars
1.1k
Forks
542
Avg merge
1d 19h
Merged PRs (30d)
137

Description

InstallationManager.update() (openhands-sdk/openhands/sdk/extensions/installation/manager.py:293-325) always re-fetches with ref=None:

return self.install(
    source=current_info.source,
    ref=None,
    repo_path=current_info.repo_path,
    force=True,
)

This ignores whatever ref was originally requested (now recorded in InstallationInfo.requested_ref, added in #4349). Calling update() on an extension pinned to a tag or commit silently un-pins it and jumps to the source's current default-branch HEAD instead of just re-syncing to the pinned ref.

This isn't hypothetical — it's reachable today via the public API for both live systems that share this code path:

  • plugins_router.py:341 (POST /installed/{plugin_name}/refresh) → service_update_pluginInstallationManager.update()
  • skills_router.py:507 (POST /installed/{skill_name}/refresh) → service_update_skillInstallationManager.update()

Prior art: earendil-works/pi (a comparable extension/package system) makes this split explicit — see packages.md:

Refs are pinned tags or commits. pi update --extensions and pi update --all do not move them to newer refs, but they do reconcile an existing clone to the configured ref.

Moving to a genuinely new ref is a separate, explicit action (pi install git:host/user/repo@new-ref), distinct from routine reconciliation.

Suggested approach: update() should default to reconciling against current_info.requested_ref instead of hardcoding ref=None:

  • If requested_ref is set, re-resolve that same ref (catches drift, e.g. a moved tag; otherwise a no-op).
  • If requested_ref is None, the install was already floating, so resolving to latest is correct, same as today.
  • Re-pinning to a different ref stays an explicit call to install(source, ref=new_ref, force=True), which already supports it — update() doesn't need a new parameter, just to stop forcing ref=None.

Note: this changes the current (undocumented) behavior of update()/the /refresh endpoints for both Plugins and Skills — worth confirming nothing relies on "refresh always jumps to latest regardless of pin" before changing the default.

This also directly informs #4351's "Check" step ("resolve the tracked source's ref") — Canvas Extensions' staged-install service will need the same reconcile-vs-repin distinction, whether by calling a fixed update() or reimplementing the same logic.

Related: #4349, #4351

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in openhands-sdk/openhands/sdk/extensions/installation/manager.py:293-325 and inspect InstallationInfo.requested_ref from #4349. Trace the refresh entry points in plugins_router.py:341 and skills_router.py:507, then verify that pinned refs remain pinned while installs with no requested ref still resolve to the latest source.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
api, backend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.