# Custom Invariants Enforced in the Claude Plugin Validation Pipeline: A Complete Technical Guide

> Discover the 11 custom invariants enforced in the Claude plugin validation pipeline. Learn how these checks guarantee security, prevent duplicates, and ensure valid submissions.

- Repository: [Anthropic/claude-plugins-community](https://github.com/anthropics/claude-plugins-community)
- Tags: how-to-guide
- Published: 2026-08-27

---

**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`](https://github.com/anthropics/claude-plugins-community/blob/main/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`](https://github.com/anthropics/claude-plugins-community/blob/main/.github/actions/validate-plugins/scripts/11-validate-invariants.sh), a GitHub Actions script that executes after the [`marketplace.json`](https://github.com/anthropics/claude-plugins-community/blob/main/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:

```bash
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`](https://github.com/anthropics/claude-plugins-community/blob/main/.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`](https://github.com/anthropics/claude-plugins-community/blob/main/.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`](https://github.com/anthropics/claude-plugins-community/blob/main/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`](https://github.com/anthropics/claude-plugins-community/blob/main/marketplace.json) (`$MP`):

```bash
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:

```bash

# 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:

```json
{
  "name": "bad‑plugin!",
  "description": "short",
  "source": {
    "url": "http://example.com/repo.git",
    "sha": "12345"
  }
}

```

- **`name` fails I11**: The exclamation mark violates the `^[a-z0-9][a-z0-9-]{1,63}$` regex.
- **`description` fails I3**: Length is 5 characters, below the 10-character minimum.
- **`source.url` fails I4**: Uses `http://` instead of required `https://`.
- **`source.sha` fails 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`](https://github.com/anthropics/claude-plugins-community/blob/main/.github/actions/validate-plugins/scripts/11-validate-invariants.sh) and applied to every entry in [`marketplace.json`](https://github.com/anthropics/claude-plugins-community/blob/main/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`](https://github.com/anthropics/claude-plugins-community/blob/main/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.