OpenHands / OpenHands/software-agent-sdk
InstallationManager.update() silently drops pinned refs (should reconcile, not force ref=None)
Nobody has claimed this yet.
- 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_plugin→InstallationManager.update()skills_router.py:507(POST /installed/{skill_name}/refresh) →service_update_skill→InstallationManager.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 --extensionsandpi update --alldo 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_refis set, re-resolve that same ref (catches drift, e.g. a moved tag; otherwise a no-op). - If
requested_refisNone, 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 forcingref=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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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