BerriAI/litellm · error · HTTPException

Registration failed: {e}

Error message

Registration failed: {e}

What it means

Catch-all 500 from register_plugin (POST /claude-code/plugins). HTTPExceptions (the 400s above, name-conflict 409 from UniqueViolationError, DB-not-connected) are re-raised untouched; everything else — unexpected DB failures, missing plugin table, serialization errors — is logged as 'Error registering plugin' and returned as this generic message with the exception string interpolated.

Source

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

        return RegisterPluginResponse(
            status="success",
            action="created",
            plugin=PluginResponse(
                id=plugin.id,
                name=plugin.name,
                version=plugin.version,
                description=plugin.description,
                source=request.source,
                enabled=plugin.enabled,
            ),
        )

    except HTTPException:
        raise
    except Exception as e:
        verbose_proxy_logger.exception("Error registering plugin: %s", e)
        raise HTTPException(
            status_code=500,
            detail={"error": f"Registration failed: {e}"},
        )


@router.get(
    "/claude-code/plugins",
    tags=["Claude Code Marketplace"],
    dependencies=[Depends(user_api_key_auth)],
    response_model=ListPluginsResponse,
)
async def list_plugins(
    enabled_only: bool = False,
    user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
    """
    List all plugins in the marketplace.

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Read the proxy log: 'Error registering plugin: ...' carries the full traceback (verbose_proxy_logger.exception) — that names the real failure.
  2. If the traceback says the table/relation does not exist, restart the proxy with DATABASE_URL set so Prisma migrations create it, or run the migrations against the database manually.
  3. If it is a connectivity error, verify Postgres is up and credentials valid, then retry the POST once recovered (registration is not idempotent — a 409 on retry means the first attempt actually landed).
  4. Confirm you are not hitting a genuine duplicate-name path via a different code path (direct 409 'already exists' responses are handled separately and will not produce this 500).
Defensive patterns

Strategy: try-catch

Try / catch

try:
    resp = client.post(f"{base}/claude-code/plugins", json=payload)
    resp.raise_for_status()
except httpx.HTTPStatusError as e:
    if e.response.status_code == 500:
        # ambiguous: the insert may or may not have committed.
        existing = client.get(f"{base}/claude-code/plugins", headers=hdr)
        if any(p["name"] == payload["name"] for p in existing.json()["plugins"]):
            return  # actually registered on the first attempt
        raise
    if e.response.status_code == 409:
        return  # name conflict - treat as registered
    raise

Prevention

When it happens

Trigger: POST /claude-code/plugins when the litellm_claudecodeplugintable does not exist yet (DB migrated by an older LiteLLM before this feature shipped), when the Postgres connection drops mid-insert, or when the manifest built from PluginSpec fails to serialize for the DB write.

Common situations: Upgrading LiteLLM to a version that introduced the plugin marketplace while pointing at an existing database that was never re-migrated; transient DB outage during a CI script that registers many plugins; prisma client in a bad state after a DB restart.

Related errors


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