BerriAI/litellm · error · RuntimeError

Cannot migrate covered tables: no salt key / master key is s

Error message

Cannot migrate covered tables: no salt key / master key is set. Set LITELLM_SALT_KEY before migrating.

What it means

Raised by LiteLLM's credential-migration flow (litellm/proxy/management_endpoints/credential_migration.py) when the pre-flight _get_salt_key() returns None: the proxy process has neither LITELLM_SALT_KEY set nor a master key to fall back to (encrypt_decrypt_utils._get_salt_key checks the env var, then master_key). Migration re-encrypts stored provider credentials by calling _rotate_master_key with the same key (algorithm-only switch), so a key must exist before any rotation runs. The RuntimeError aborts before a single table is touched.

Source

Thrown at litellm/proxy/management_endpoints/credential_migration.py:578

    """Re-encrypt the tables already covered by ``_rotate_master_key`` (model
    table, credentials, MCP credential/env tables, config environment_variables)
    by running that orchestrator in *same-key* mode. With the AES gate on, the
    re-encrypt writes land in ``v2:`` format.

    ``_rotate_master_key`` returns no counts, so we bracket it with read-only
    scans: the pre-scan's legacy total minus the post-scan's gives the number
    actually migrated per location, and the post-scan supplies the residual /
    already-v2 / scanned figures. Returns one report per covered location.
    """
    from litellm.proxy.management_endpoints.key_management_endpoints import (
        _rotate_master_key,
    )

    pre: Final = {r.location: r for r in await _scan_covered_tables(prisma_client)}

    current_key: Final = _get_salt_key()
    if current_key is None:
        raise RuntimeError(
            "Cannot migrate covered tables: no salt key / master key is set. Set LITELLM_SALT_KEY before migrating."
        )
    await _rotate_master_key(
        prisma_client=cast("PrismaClient", prisma_client),
        user_api_key_dict=cast("UserAPIKeyAuth", user_api_key_dict),
        current_master_key=current_key,
        new_master_key=current_key,  # same key, algorithm-only switch
    )

    post: Final = await _scan_covered_tables(prisma_client)
    for post_report in post:
        pre_report = pre.get(post_report.location)
        pre_legacy = pre_report.legacy if pre_report else 0
        # Everything that was legacy before and is no longer legacy now was
        # converted this run.
        post_report.migrated = max(0, pre_legacy - post_report.legacy)
    return post

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Export LITELLM_SALT_KEY in the proxy process environment (deployment env vars, docker -e, or systemd Environment=) and restart the proxy
  2. Alternatively ensure a master key (LITELLM_MASTER_KEY / general_settings.master_key) is configured, since _get_salt_key() falls back to it
  3. Verify inside the same container/process that runs the migration: printenv LITELLM_SALT_KEY must be non-empty
  4. Re-run the migration endpoint and confirm the pre/post scan reports are returned

Example fix

# before
podman run -p 4000:4000 litellm/litellm --config /app/config.yaml   # no salt key -> migration raises RuntimeError

# after
podman run -p 4000:4000 -e LITELLM_SALT_KEY=sk-salt-... litellm/litellm --config /app/config.yaml
Defensive patterns

Strategy: validation

Validate before calling

import os


def credential_migration_ready() -> bool:
    return bool(os.getenv("LITELLM_SALT_KEY") or os.getenv("LITELLM_MASTER_KEY"))


if not credential_migration_ready():
    raise SystemExit("Set LITELLM_SALT_KEY before running credential migration")

Try / catch

try:
    report = await migrate_covered_tables(prisma_client, user_api_key_dict)
except RuntimeError as e:
    if "no salt key" in str(e):
        # environment problem, not a data problem: fix env and re-run once
        raise SystemExit("LITELLM_SALT_KEY missing in this process") from e
    raise

Prevention

When it happens

Trigger: Invoking the covered-tables credential migration endpoint while the proxy process has no LITELLM_SALT_KEY exported and no master key configured; running the migration from a new pod, CI job, or shell that did not inherit the proxy's secret environment.

Common situations: Ops schedules a credentials re-encryption after upgrading LiteLLM, but the Kubernetes deployment / Docker env is missing LITELLM_SALT_KEY; the key lives in a .env file the proxy never loads; the migration is invoked from a different container than the one holding the secret.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


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