OpenHands / OpenHands/extensions

Marketplace catalog unreachable via symlinked .claude-plugin path (breaks plugin-directory / `github://openhands/extensions`)

Open Beginner friendly
#394 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Python
Stars
148
Forks
90
Avg merge
1d 17h
Merged PRs (30d)
36

Description

Summary

When the plugin-directory is configured with MARKETPLACE_SOURCE=github://openhands/extensions, the marketplace catalog does not render — the Plugin Directory shows an empty catalog.

Root cause: the catalog is exposed through a double symlink, and raw.githubusercontent.com does not dereference symlinks through directory paths.

How plugin-directory fetches the catalog

openhands/plugin-directory builds a hardcoded raw URL from the source config (server/app/services/marketplace_service.py):

CATALOG_PATH = ".claude-plugin/marketplace.json"
url = f"https://raw.githubusercontent.com/{owner}/{repo}/{ref}/{catalog_path}"

So it requests exactly:

https://raw.githubusercontent.com/openhands/extensions/main/.claude-plugin/marketplace.json

The symlink chain in this repo

Traced via the GitHub git Trees API (mode=120000 = symlink):

Path Git mode Type Target
.claude-plugin 120000 symlink .plugin
.plugin/marketplace.json 120000 symlink ../marketplaces/openhands-extensions.json
marketplaces/openhands-extensions.json 100644 real file (the actual catalog)

Verification

raw.githubusercontent.com serves the raw blob content stored in git. A symlink is stored as a blob whose content is the link-target text, and the raw server does not traverse symlinked directory paths:

  • .claude-plugin/marketplace.jsonHTTP 404 (symlinked directory not traversable)
  • .plugin/marketplace.json → returns the literal string ../marketplaces/openhands-extensions.json (the symlink blob body), not the JSON catalog — so even if level 1 resolved, JSON parsing would fail
  • marketplaces/openhands-extensions.jsonHTTP 200, the real catalog JSON

As a result, plugin-directory's fetch_catalog() raises CatalogUnavailableError on the 404, the plugins list comes back empty, and nothing renders. This is a server-side (GitHub raw) limitation, not something the plugin-directory client can work around — the catalog simply never arrives.

Suggested fix

  1. In this repo (simplest): commit a real .claude-plugin/marketplace.json file (or a real .claude-plugin/ directory) instead of the symlink chain, so the catalog is reachable at the path plugin-directory (and other consumers using the standard .claude-plugin/marketplace.json convention) expects.

Happy to open a PR for either direction if helpful.


This issue was created by an AI agent (OpenHands) on behalf of a user.

Contributor guide

No contributing guide indexed for this repository

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

Inspect marketplaces/openhands-extensions.json and the current .claude-plugin symlink path, then verify the raw GitHub URL used by plugin-directory. Make the catalog available as a real file at .claude-plugin/marketplace.json and confirm that the raw URL returns the catalog JSON instead of a 404 or symlink target.

Written by the indexing model from the issue text.

Assessment

Tech stack
github, python
Domain
tooling
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.