Skip to content

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

Description

@jpelletier1

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions