BerriAI/litellm · error · HTTPException

Plugin name must be kebab-case (lowercase letters, numbers,

Error message

Plugin name must be kebab-case (lowercase letters, numbers, hyphens)

What it means

400 from register_plugin: the plugin name must match ^[a-z0-9-]+$ — kebab-case only (lowercase letters, digits, hyphens). The name is used as the primary key/unique identifier in litellm_claudecodeplugintable and as the marketplace identifier, so LiteLLM enforces the restrictive format up front, before source validation and the uniqueness check.

Source

Thrown at litellm/proxy/anthropic_endpoints/claude_code_endpoints/claude_code_marketplace.py:277

        ```bash
        curl -X POST http://localhost:4000/claude-code/plugins \\
          -H "Authorization: Bearer sk-..." \\
          -H "Content-Type: application/json" \\
          -d '{
            "name": "my-plugin",
            "source": {"source": "github", "repo": "org/my-plugin"},
            "version": "1.0.0",
            "description": "My awesome plugin"
          }'
        ```
    """
    from prisma.errors import UniqueViolationError

    try:
        prisma_client: Final = await _get_prisma_client()

        if not re.match(r"^[a-z0-9-]+$", request.name):
            raise HTTPException(
                status_code=400,
                detail={"error": "Plugin name must be kebab-case (lowercase letters, numbers, hyphens)"},
            )

        _validate_plugin_source(request.source)

        existing: Final[_PluginRecord | None] = await ClaudeCodePluginRepository(prisma_client).table.find_unique(
            where={"name": request.name}
        )
        if existing:
            raise _name_conflict_error(request.name)

        manifest: Final[Mapping[str, object]] = _build_plugin_manifest(request.name, request)

        try:
            plugin: Final[_PluginRecord] = await ClaudeCodePluginRepository(prisma_client).table.create(
                data={
                    "name": request.name,

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Slugify the name to lowercase kebab-case: "my-plugin".
  2. Strip/replace disallowed characters before sending: lowercase, spaces and underscores to hyphens, drop dots and symbols.
  3. If updating an existing plugin, note the name is also the route parameter — use the same kebab-case name in PUT /claude-code/plugins/{name}.

Example fix

# before
curl -X POST http://localhost:4000/claude-code/plugins \
  -d '{"name": "Data_Cleaner", "source": {"source": "github", "repo": "org/dc"}}'

# after
curl -X POST http://localhost:4000/claude-code/plugins \
  -d '{"name": "data-cleaner", "source": {"source": "github", "repo": "org/dc"}}'
Defensive patterns

Strategy: validation

Validate before calling

import re

def slugify(name: str) -> str:
    s = re.sub(r"[^a-zA-Z0-9]+", "-", name.strip().lower()).strip("-")
    if not re.fullmatch(r"[a-z0-9-]+", s):
        raise ValueError(f"cannot slugify {name!r} to kebab-case")
    return s

payload["name"] = slugify(payload["name"])  # 'Data_Cleaner' -> 'data-cleaner'

Type guard

const KEBAB = /^[a-z0-9-]+$/;
function isKebabName(n: unknown): n is string {
  return typeof n === 'string' && n.length > 0 && KEBAB.test(n);
}

Prevention

When it happens

Trigger: POST /claude-code/plugins with names like "My_Plugin" (uppercase + underscore), "my plugin" (space), "my.plugin" (dot), "plugin--name!!", or an empty string — anything containing characters outside [a-z0-9-].

Common situations: Reusing a display name or npm-style package name (my_plugin) as the plugin id; auto-generating names from titles without slugifying; trailing whitespace from form input.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/b353934e72789425. Report an issue: GitHub.