fix: make root marketplace catalog real files (not symlinks) - #395
fix: make root marketplace catalog real files (not symlinks)#395jpelletier1 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.
|
@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 PR needs a couple of things fixed before OpenHands can review it:
Push an update once this is addressed and this check re-runs automatically. This is an automated check - no AI was used to generate this comment. |
3 similar comments
|
👋 This PR needs a couple of things fixed before OpenHands can review it:
Push an update once this is addressed and this check re-runs automatically. This is an automated check - no AI was used to generate this comment. |
|
👋 This PR needs a couple of things fixed before OpenHands can review it:
Push an update once this is addressed and this check re-runs automatically. This is an automated check - no AI was used to generate this comment. |
|
👋 This PR needs a couple of things fixed before OpenHands can review it:
Push an update once this is addressed and this check re-runs automatically. This is an automated check - no AI was used to generate this comment. |
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.