# Invariant Checks Enforced by the validate-plugins Action in the Claude Plugins Community

> Discover the eleven invariant checks enforced by the validate-plugins GitHub Action in the Claude Plugins Community. Learn about sorting, description length, HTTPS, SHA pinning, filename, and safety rules.

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

---

**The validate-plugins GitHub Action enforces eleven hard-coded policy invariants (I1–I11) that govern alphabetical sorting, description length, HTTPS enforcement, SHA pinning, filename consistency, and shell-character safety across the Claude plugin marketplace.**

The `validate-plugins` composite action serves as the policy gatekeeper for the `anthropics/claude-plugins-community` repository. After canonical schema validation completes via `claude plugin validate`, **step 11** executes a Bash script that applies these marketplace invariants independently of the upstream Zod schema. This ensures safety, consistency, and quality standards before any external code enters the ecosystem.

## The Eleven Policy Invariants

The core 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). Each invariant detection emits a GitHub annotation via the `flag` function and can be downgraded to a warning via the `warn-invariants` input.

### I1: Alphabetical Sorting

The marketplace entries must be sorted alphabetically by the `name` field using case-insensitive comparison. At lines 91–94 of the validation script, the implementation extracts names using `jq`, compares the original order against a sorted copy, and flags "I1" when discrepancies exist.

### I2: Unique Plugin Names

Every plugin name must be unique across the entire marketplace. Lines 95–98 detect duplicates by grouping entries with `jq` and flag "I2" when any name appears more than once, preventing namespace collisions.

### I3: Description Length and Whitespace

Descriptions must contain between **10 and 2000 characters** and cannot have leading or trailing whitespace. The entry loop at lines 14–19 calculates string length (`len=${#desc}`) and checks whitespace boundaries, flagging "I3" for violations of either constraint.

### I4: HTTPS-Only URLs

External sources must use HTTPS URLs matching the pattern `^https://[A-Za-z0-9./_-]+$` (or the `owner/repo` shorthand for GitHub repositories). Lines 30–35 apply this regex test to `source.url` and flag "I4" when non-HTTPS protocols or malformed paths are detected.

### I5: SHA Pinning

Every external source must specify a **40-character lowercase hexadecimal SHA** (matching `^[0-9a-f]{40}$`). Lines 36–48 validate this format and flag "I5" unless the plugin appears in the `sha-exempt` input list, ensuring reproducible builds and supply-chain integrity.

### I6: Filename Matches Plugin Name

In per-file repository configurations, each `plugins/<name>.json` file must declare a `.name` field identical to its filename (without extension). Lines 60–66 iterate over `$ENTRIES_DIR/*.json` and flag "I6" when the basename mismatches the declared name, preventing file-to-content desynchronization.

### I7: No Direct Marketplace Edits

Pull requests must modify individual entry files rather than the assembled [`marketplace.json`](https://github.com/anthropics/claude-plugins-community/blob/main/marketplace.json) directly. Lines 67–69 use `git diff` combined with `grep -qxF` to detect changes to the marketplace file itself, flagging "I7" when direct edits are detected and enforcing proper contribution workflow.

### I8: Vendored Source Existence

For vendored sources using local paths (e.g., `"./path"`), the directory must contain a [`.claude-plugin/plugin.json`](https://github.com/anthropics/claude-plugins-community/blob/main/.claude-plugin/plugin.json) file. The existence check at lines 71–84 validates this structure and flags "I8" if the required plugin metadata file is missing.

### I9: Shell-Character Safety

All string fields under `source` must exclude shell metacharacters including `$`, backticks (`` ` ``), semicolons (`;`), ampersands (`&`), pipes (`|`), parentheses (`(`, `)`), redirection operators (`<`, `>`), spaces, tabs, quotes, and backslashes. Lines 50–56 invoke `has_unsafe_chars` from [`.github/actions/validate-plugins/lib/common.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/.github/actions/validate-plugins/lib/common.sh) (lines 37–44) to flag "I9" when dangerous characters appear, preventing command injection vulnerabilities.

### I10: Hidden Unicode Detection

Plugin names and descriptions must not contain zero-width or bidirectional control characters (U+200B, U+200C, U+200D, U+200E, U+200F, U+202A–202E, U+2066–2069, U+FEFF). Lines 99–102 check against the pre-computed `HIDDEN_UNI` character set and flag "I10" if any hidden Unicode appears, mitigating homograph attacks and visual spoofing.

### I11: Name Format Specification

Plugin names must match the regex `^[a-z0-9][a-z0-9-]{1,63}$`, enforcing lowercase alphanumerics with optional hyphens and a length of **2–64 characters**. Lines 7–10 perform this validation inside the entry loop and flag "I11" when names contain uppercase letters, underscores, or invalid characters.

## Configuration and Error Handling

The action supports granular control over invariant enforcement through two inputs:

- **`warn-invariants`**: Accepts a space-separated list of invariant codes (default: `I1 I3 I5 I8`) that emit warnings rather than errors. This allows marketplace maintainers to enforce strictness gradually without blocking contributions.
- **`scope-errors-to-changed`**: When set to `true`, violations on entries not modified by the current pull request downgrade from error to warning. This prevents stale base-branch defects from blocking unrelated updates.

## Implementation Architecture

The invariant validation layer consists of two critical files:

- **[`.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)**: The core script executed as step 11, implementing all eleven invariant checks and emitting GitHub workflow annotations.
- **[`.github/actions/validate-plugins/lib/common.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/.github/actions/validate-plugins/lib/common.sh)**: Shared utility library providing the `has_unsafe_chars` helper function used by I9 to detect shell metacharacters.

Each violation generates machine-readable annotations in the format:

```text
::error file=marketplace.json::invariant I2: duplicate plugin names: dup

```

## Example Violations

Below are minimal JSON snippets that trigger specific invariants when processed by the action:

**I1 (Unsorted Names):**

```json
{
  "plugins": [
    {"name":"zzz","description":"Valid description","source":"./z"},
    {"name":"aaa","description":"Valid description","source":"./a"}
  ]
}

```

**I4 (Non-HTTPS URL):**

```json
{
  "plugins": [
    {"name":"badurl","description":"Valid description","source":{"source":"url","url":"http://example.com/x","sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}
  ]
}

```

**I5 (Missing SHA):**

```json
{
  "plugins": [
    {"name":"nosha","description":"Valid description","source":{"source":"url","url":"https://github.com/x/y"}}
  ]
}

```

**I9 (Unsafe Shell Characters):**

```json
{
  "plugins": [
    {"name":"shell","description":"Valid description","source":{"source":"url","url":"https://github.com/x/y;rm -rf /","sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}
  ]
}

```

**I10 (Zero-Width Space in Description):**

```json
{
  "plugins": [
    {"name":"zwsp","description":"hello​world ten chars","source":"./x"}
  ]
}

```

**I11 (Invalid Name Format):**

```json
{
  "plugins": [
    {"name":"Bad_Name","description":"Valid description","source":"./x"}
  ]
}

```

## Summary

- The `validate-plugins` action enforces eleven specific invariants (I1–I11) through [`.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), operating independently of JSON schema validation.
- **I1** and **I2** ensure alphabetical ordering and name uniqueness across the marketplace catalog.
- **I3** validates description length constraints (10–2000 characters) and prohibits boundary whitespace.
- **I4** mandates HTTPS URLs while **I5** requires 40-character hexadecimal SHA pinning for external sources to prevent supply-chain attacks.
- **I6**, **I7**, and **I8** govern repository structure, prohibiting direct [`marketplace.json`](https://github.com/anthropics/claude-plugins-community/blob/main/marketplace.json) edits and verifying vendored path existence.
- **I9** provides security hardening against shell injection via the `has_unsafe_chars` helper in [`lib/common.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/lib/common.sh).
- **I10** blocks hidden Unicode characters (U+200B, bidi controls) that enable visual spoofing.
- **I11** enforces strict naming conventions: lowercase alphanumeric with hyphens, 2–64 characters, matching `^[a-z0-9][a-z0-9-]{1,63}$`.
- Violations can be downgraded to warnings via `warn-invariants` or scoped to changed files via `scope-errors-to-changed`.

## Frequently Asked Questions

### How can I configure certain invariants to show warnings instead of errors?

Pass a space-separated list of invariant codes to the `warn-invariants` input. The default configuration (`I1 I3 I5 I8`) treats these specific invariants as warnings, while all others fail the workflow immediately. This allows gradual adoption of stricter policies without blocking existing contributions.

### Why does the action require SHA pinning for external plugins?

Invariant **I5** mandates 40-character lowercase hexadecimal SHAs to ensure reproducible builds and prevent supply-chain attacks where upstream repositories might change code unexpectedly. Only plugins explicitly listed in the `sha-exempt` input may omit this requirement, providing a controlled exception mechanism for trusted sources.

### What is the difference between I6 and I7?

**I6** validates that individual JSON entry filenames match their internal `name` field in per-file repository setups, ensuring the file [`plugins/example.json`](https://github.com/anthropics/claude-plugins-community/blob/main/plugins/example.json) contains `"name": "example"`. **I7** prevents contributors from manually editing the assembled [`marketplace.json`](https://github.com/anthropics/claude-plugins-community/blob/main/marketplace.json) file directly, enforcing that all changes flow through proper individual entry files to maintain automation integrity.

### How does the action prevent shell injection attacks?

Invariant **I9** utilizes the `has_unsafe_chars` function defined in [`.github/actions/validate-plugins/lib/common.sh`](https://github.com/anthropics/claude-plugins-community/blob/main/.github/actions/validate-plugins/lib/common.sh) (lines 37–44) to scan all strings under the `source` object for metacharacters including `$`, backticks, semicolons, pipes, and redirection operators. If detected, the action flags "I9" and blocks the submission before any shell execution occurs, neutralizing command injection vectors.