Custom Invariants Enforced in the Claude Plugin Validation Pipeline: A Complete Technical Guide
The Claude plugin validation pipeline enforces 11 hardening invariants (I1–I11) that extend beyond JSON Schema validation to guarantee alphabetical ordering, prevent duplicate entries, mandate HTTPS sources, enforce 40-character SHA hashes, block shell metacharacters, and eliminate hidden Unicode characters in every plugin submission.
The anthropics/claude-plugins-community repository maintains strict quality controls through a Bash-based validation pipeline that scrutinizes every plugin entry in the assembled marketplace.json. These custom invariants protect the marketplace ecosystem from malformed manifests, security vulnerabilities, and naming collisions. Contributors must satisfy all eleven rules before their plugins appear in the Claude marketplace.
Overview of the Validation Architecture
The validation logic resides in .github/actions/validate-plugins/scripts/11-validate-invariants.sh, a GitHub Actions script that executes after the marketplace.json assembly phase. Unlike standard JSON Schema validation, these invariants implement hardening rules specific to the Claude plugin ecosystem, including Unicode safety checks and cryptographic hash verification.
The script iterates over every entry using jq and reports violations via GitHub Actions annotations (::error or ::warning). When the SCOPE_ERRORS_TO_CHANGED environment variable is active, violations on unchanged entries downgrade to warnings, preventing pre-existing defects from blocking unrelated pull requests.
The 11 Custom Invariants Explained
The validation pipeline categorizes checks into structural integrity, content quality, security enforcement, and workflow protection.
Structural Integrity Checks (I1–I2)
I1 – Alphabetical Ordering: The plugins[] array must be sorted alphabetically (case-insensitive) by the name field. The script validates this using jq one-liners (lines 91–98) rather than in the entry loop.
I2 – Duplicate Detection: No two entries may share the same name value. This prevents namespace collisions in the marketplace registry.
Content Quality and Safety (I3, I10–I11)
I3 – Description Constraints: The description field must contain between 10 and 2000 characters and cannot have leading or trailing whitespace. The implementation uses ${#desc} for length checking and sed with [[:space:]] character classes for whitespace validation:
len=${#desc}
(( len < 10 || len > 2000 )) && flag "I3" "$name: description length $len …"
[[ $desc != "$(printf '%s' "$desc" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')" ]] && \
flag "I3" "$name: description has leading/trailing whitespace"
I10 – Hidden Unicode Blocking: Both name and description fields are scanned for zero-width and bidirectional control characters. The script concatenates $name$desc and checks against the HIDDEN_UNI character class to prevent homograph attacks and invisible payload injection.
I11 – Name Shape Validation: Plugin names must match the regular expression ^[a-z0-9][a-z0-9-]{1,63}$, enforcing lowercase alphanumeric characters with hyphens, starting with a letter or number, and between 2–64 characters total. While this defaults to Error severity, it downgrades to a Warning when scoping is active for unchanged entries.
Security and Source Validation (I4–I5, I8–I9)
I4 – HTTPS Source URLs: Every source.url or source.repo must use the https:// protocol or the owner/repo shorthand format. Plain HTTP URLs trigger immediate rejection.
I5 – SHA-40 Hex Requirement: External sources must provide a 40-character hexadecimal SHA hash. Missing SHA values are permitted only for plugin names explicitly listed in the SHA_EXEMPT environment variable.
I8 – Vendored Source Presence: When source references a local filesystem path, that directory must contain a valid .claude-plugin/plugin.json manifest file (validated at lines 172–184).
I9 – Shell Metacharacter Sanitization: All string fields under the .source object—including vendored paths—are scanned for shell metacharacters using helper functions from .github/actions/validate-plugins/lib/common.sh (specifically has_unsafe_chars). This prevents command injection via malicious path strings.
Workflow Protection (I6–I7)
I6 – Filename Matching: In per-file repository mode, each *.json file’s basename must exactly match the plugin’s .name field, ensuring consistency between filesystem and manifest metadata.
I7 – Direct Edit Protection: Pull requests must not modify the assembled marketplace.json file directly; contributors should edit individual entry files only. This invariant preserves the automated assembly workflow.
Implementation Details in 11-validate-invariants.sh
The core validation loop processes entries from the temporary marketplace.json ($MP):
while IFS= read -r entry; do
name=$(jq -r '.name' <<<"$entry")
desc=$(jq -r '.description' <<<"$entry")
# I11 – name shape validation
[[ $name =~ ^[a-z0-9][a-z0-9-]{1,63}$ ]] || flag "I11" "$name: name does not match …"
# I10 – hidden Unicode detection
[[ $name$desc == *[${HIDDEN_UNI}]* ]] && flag "I10" "$name: hidden‑Unicode …"
# I3 – description length & whitespace
len=${#desc}
(( len < 10 || len > 2000 )) && flag "I3" "$name: description length $len …"
done < <(jq -c '.plugins[]' -- "$MP")
Source-related checks (I4, I5, I9) execute between lines 122–158, iterating over all string fields under .source to validate URL protocols, hash lengths, and character safety simultaneously.
Error reporting uses GitHub Actions workflow commands:
# Standard error (fails the build)
printf '::error %s::invariant %s: %s\n' "$loc" "$code" "$msg"
# Downgraded warning (when scoping is active)
printf '::warning %s::invariant %s: %s\n' "$loc" "$code" "$msg"
Example Invariant Violations
The following manifest triggers multiple invariant failures:
{
"name": "bad‑plugin!",
"description": "short",
"source": {
"url": "http://example.com/repo.git",
"sha": "12345"
}
}
namefails I11: The exclamation mark violates the^[a-z0-9][a-z0-9-]{1,63}$regex.descriptionfails I3: Length is 5 characters, below the 10-character minimum.source.urlfails I4: Useshttp://instead of requiredhttps://.source.shafails I5: Contains only 5 characters instead of the required 40-character hexadecimal string.
When submitted via pull request, the validation step emits four ::error annotations, causing the workflow to abort and block merging.
Summary
The Claude plugin validation pipeline enforces eleven critical invariants to maintain marketplace integrity:
- I1–I2 ensure structural consistency through alphabetical ordering and duplicate prevention.
- I3, I10–I11 enforce content standards for descriptions and names, including Unicode safety and regex pattern matching.
- I4–I5 mandate cryptographic verification and secure transport for external sources.
- I8–I9 validate local vendored sources and prevent shell injection attacks.
- I6–I7 protect the automated assembly workflow from direct metadata edits and filename mismatches.
These rules are implemented in .github/actions/validate-plugins/scripts/11-validate-invariants.sh and applied to every entry in marketplace.json before publication.
Frequently Asked Questions
What happens if my plugin fails an invariant check?
The validation pipeline emits GitHub Actions annotations via ::error workflow commands, which appear directly in the pull request diff. All invariant violations must be resolved before the workflow permits merging, though pre-existing defects on unchanged entries may be downgraded to warnings when scoping is enabled.
Can I request an exemption from the SHA-40 requirement?
Yes. The I5 invariant supports exemptions through the SHA_EXEMPT environment variable. If your plugin name appears in this list, the validation pipeline permits missing SHA values in the source.sha field, though providing a 40-character hexadecimal hash remains the recommended practice.
How does the validation pipeline handle pre-existing violations?
When SCOPE_ERRORS_TO_CHANGED is set to true, the script activates scoping logic that compares changed files against the pull request diff. Violations occurring in unchanged entries are downgraded from errors to warnings using the ::warning annotation format, ensuring that legacy issues do not block new contributions.
What is the maximum length for a Claude plugin name?
According to the I11 invariant implemented in 11-validate-invariants.sh, plugin names must match the regex ^[a-z0-9][a-z0-9-]{1,63}$, which permits a maximum of 64 characters total (the initial character plus 1–63 additional characters). Names must start with a lowercase letter or digit and may contain only lowercase alphanumeric characters and hyphens.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →