fix: make root marketplace catalog real files (not symlinks) - #395
Open
jpelletier1 wants to merge 3 commits into
Open
fix: make root marketplace catalog real files (not symlinks)#395jpelletier1 wants to merge 3 commits into
jpelletier1 wants to merge 3 commits into
Conversation
raw.githubusercontent.com does not dereference git symlinks through directory paths, so the double symlink chain (.claude-plugin -> .plugin -> ../marketplaces/openhands-extensions.json) made the catalog unreachable at the standard .claude-plugin/marketplace.json path. Consumers using github://openhands/extensions (e.g. openhands/plugin-directory) got a 404 or the literal symlink body and saw an empty Plugin Directory. Replace the root vendor symlinks with real marketplace.json copies that mirror the canonical marketplaces/openhands-extensions.json byte-for-byte. Add a root-marketplace sync task to scripts/sync_extensions.py to keep them in sync (and CI --check enforces it), plus tests. Closes #394 Co-authored-by: openhands <openhands@all-hands.dev>
Collapse the three root marketplace copies into a single canonical one at .plugin/marketplace.json (this repo's canonical manifest dir). Drop the root .claude-plugin/ and .codex-plugin/ mirrors entirely - they duplicated the catalog with no raw-URL benefit, since raw.githubusercontent.com cannot dereference symlinks and the real file already lives at .plugin/. Per-skill/per-plugin .claude-plugin and .codex-plugin vendor symlinks (within skills/ and plugins/) are unrelated and remain. Note: openhands/plugin-directory hardcodes .claude-plugin/marketplace.json as its fetch path, so it must be updated to fetch .plugin/marketplace.json for this to resolve #394 on its side. Co-authored-by: openhands <openhands@all-hands.dev>
This reverts commit 640cf14.
jpelletier1
marked this pull request as ready for review
July 8, 2026 14:25
Contributor
Author
|
@jpshackelford As someone passionate about Plugins, I wanted to see if you had a chance to review this PR. Specifically, the symlinks we use in the OpenHands/extensions repo makes it difficult to render the full plugin marketplace in the Plugin Directory -- this attempts to fix that by creating an actual marketplace.json in the respective folders and keeping them in sync. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
When the plugin-directory is configured with
MARKETPLACE_SOURCE=github://openhands/extensions, the marketplace catalog does not render (empty Plugin Directory).Root cause: the catalog was exposed through a double symlink chain, and
raw.githubusercontent.comdoes not dereference git symlinks (it serves the literal symlink target text; for symlinked directory paths it returns 404):.claude-plugin(symlink) ->.plugin.plugin/marketplace.json(symlink) ->../marketplaces/openhands-extensions.jsonVerified empirically against
main:GET .plugin/marketplace.jsonreturns HTTP 200 but the body is the literal string../marketplaces/openhands-extensions.json(not JSON), andGET .claude-plugin/marketplace.jsonreturns 404 (symlinked directory not traversable). So consumers doingGET+json.loads()get a 404 or a parse error and an empty catalog. This is a server-side (GitHub raw) limitation the plugin-directory client cannot work around, so the catalog simply never arrives.Summary
.claude-plugin,.codex-plugin,.plugin/marketplace.json) with real directories + realmarketplace.jsonfiles that each mirror the canonicalmarketplaces/openhands-extensions.jsonbyte-for-byte (canonicalized JSON). Git mode changed from120000(symlink) to100644(regular file). The catalog is now reachable at every standard.claude-plugin/marketplace.json/.codex-plugin/marketplace.json/.plugin/marketplace.jsonpath over the raw URL.root-marketplacesync task toscripts/sync_extensions.pythat keeps all three root copies in sync with the canonical file and reports drift in--checkmode. Since CI runspython scripts/sync_extensions.py --checkwith no task filter, the new task is automatically enforced on every PR.tests/test_sync_extensions.py(TestRootMarketplace) verifying the real-repo files are non-symlinks, match the canonical catalog, are valid JSON, and that the sync task detects/fixes both the original symlink chain and stale copies.AGENTS.mdwith the invariant (root catalog copies are real files, never symlinks).Issue Number
Closes #394
How to Test
pip install pyyaml pytestpython scripts/sync_extensions.py --check- exits 0 (only the pre-existing, non-blocking coverage warning forplugins/issue-duplicate-checkerremains).python -m pytest tests/test_sync_extensions.py -q- 40 passed.100644, not120000):git ls-files -s -- .claude-plugin .codex-plugin .pluginmain,curl -fsSL https://raw.githubusercontent.com/OpenHands/extensions/main/.claude-plugin/marketplace.jsonreturns the catalog JSON (HTTP 200) instead of 404. (Cannot verify pre-merge from this branch since the raw URL servesmain.)Video/Screenshots
N/A - file-system / raw-URL fix with no UI surface.
Notes
.claude-plugin/marketplace.json,.codex-plugin/marketplace.json, and.plugin/marketplace.jsonfiles are now generated copies; edit the canonicalmarketplaces/openhands-extensions.jsonand runpython scripts/sync_extensions.py(no args) to refresh all three. Do not reintroduce symlinks at these paths..claude-pluginand.codex-pluginvendor symlinks withinskills/andplugins/are unrelated and unchanged (managed by the existingsymlinkssync task).This PR was created by an AI agent (OpenHands) on behalf of a user.