From 7a66f74e52f3ef981fd8c747a94b79116c71329b Mon Sep 17 00:00:00 2001 From: Mohamed Ragab Date: Thu, 13 Aug 2026 18:59:55 +0300 Subject: [PATCH] Fix Claude manifest repository type so the plugin can install at all MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit .claude-plugin/plugin.json declared repository as an npm-style {type, url} object. Claude Code's manifest schema requires a string and rejects the whole plugin when the type is wrong, so "claude plugin install paymob-integration@paymob" — the primary install path in the README — failed for every user: Failed to install plugin "paymob-integration@paymob": has an invalid manifest file at .claude-plugin/plugin.json Validation errors: repository: Invalid input: expected string, received object .codex-plugin/plugin.json already used the string form, so only the Claude manifest was affected and the Codex path kept working, which is why this went unnoticed. "claude plugin validate ." also missed it: pointed at a directory it validates only marketplace.json, and the plugin manifest has to be named explicitly. validate.py checked that manifest fields were present but never their types, so it passed too. It now type-checks the Claude manifest's string fields, author, and keywords. --- .claude-plugin/plugin.json | 5 +---- scripts/validate.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index c4e9b67..93a758d 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -7,10 +7,7 @@ "email": "mohamedragab@paymob.com" }, "homepage": "https://github.com/PaymobAccept/Paymob-AI-Integration-Skill", - "repository": { - "type": "git", - "url": "https://github.com/PaymobAccept/Paymob-AI-Integration-Skill.git" - }, + "repository": "https://github.com/PaymobAccept/Paymob-AI-Integration-Skill", "license": "MIT", "keywords": [ "paymob", diff --git a/scripts/validate.py b/scripts/validate.py index 4d24f6b..46cf7db 100644 --- a/scripts/validate.py +++ b/scripts/validate.py @@ -216,6 +216,22 @@ def validate_manifests(skill_name: str, errors: list[str]) -> str: if codex.get("version") != claude.get("version"): errors.append("Codex and Claude plugin versions must match") + # Claude Code rejects the whole plugin when a manifest field has the wrong + # type, so the plugin becomes uninstallable. `repository` as an npm-style + # {type, url} object is the easy mistake: it is valid package.json and + # invalid here. Checking presence alone never caught it. + for field in ("name", "version", "description", "homepage", "repository", "license"): + value = claude.get(field) + if value is not None and not isinstance(value, str): + errors.append( + f"Claude manifest {field!r} must be a string, not " + f"{type(value).__name__}; a wrong type makes the plugin uninstallable" + ) + if claude.get("author") is not None and not isinstance(claude.get("author"), dict): + errors.append("Claude manifest 'author' must be an object") + if claude.get("keywords") is not None and not isinstance(claude.get("keywords"), list): + errors.append("Claude manifest 'keywords' must be an array") + for manifest_name, manifest in (("Codex", codex), ("Claude", claude)): for field in ("skills", "mcpServers"): value = manifest.get(field)