pypa/pip · error · InvalidLicenseExpression

Unknown license exception: {token!r}

Error message

Unknown license exception: {token!r}

What it means

Raised by `canonicalize_license_expression` when a token follows the `WITH` operator but is not a recognized SPDX license exception identifier. SPDX grammar allows `<license> WITH <exception>` only for enumerated exceptions (e.g. `Classpath-exception-2.0`, `GCC-exception-3.1`); arbitrary tokens after `WITH` are rejected.

Source

Thrown at src/pip/_vendor/packaging/licenses/__init__.py:159

    python_expression = " ".join(python_tokens)
    try:
        compile(python_expression, "", "eval")
    except SyntaxError:
        message = f"Invalid license expression: {raw_license_expression!r}"
        raise InvalidLicenseExpression(message) from None

    # Take a final pass to check for unknown licenses/exceptions.
    normalized_tokens = []
    for token in tokens:
        if token in {"or", "and", "with", "(", ")"}:
            normalized_tokens.append(token.upper())
            continue

        if normalized_tokens and normalized_tokens[-1] == "WITH":
            if token not in EXCEPTIONS:
                message = f"Unknown license exception: {token!r}"
                raise InvalidLicenseExpression(message)

            normalized_tokens.append(EXCEPTIONS[token]["id"])
        else:
            if token.endswith("+"):
                final_token = token[:-1]
                suffix = "+"
            else:
                final_token = token
                suffix = ""

            if final_token.startswith("licenseref-"):
                if not license_ref_allowed.match(final_token):
                    message = f"Invalid licenseref: {final_token!r}"
                    raise InvalidLicenseExpression(message)
                normalized_tokens.append(license_refs[final_token] + suffix)
            else:
                if final_token not in LICENSES:
                    message = f"Unknown license: {final_token!r}"

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Use the exact SPDX exception identifier from https://spdx.org/licenses/exceptions-index.html
  2. If you need a custom exception, express it as a separate LicenseRef or drop the WITH clause
  3. Upgrade packaging to a newer version with an up-to-date SPDX list

Example fix

// before
license = "GPL-2.0-only WITH ClasspathException"
// after
license = "GPL-2.0-only WITH Classpath-exception-2.0"
Defensive patterns

Strategy: validation

Validate before calling

from packaging.licenses._spdx import EXCEPTIONS
def validate_with_clause(expr: str) -> None:
    tokens = expr.upper().split()
    for i, t in enumerate(tokens):
        if tokens[i-1] == 'WITH' and t not in EXCEPTIONS:
            raise ValueError(f'unknown SPDX exception {t!r}')

Type guard

from packaging.licenses._spdx import EXCEPTIONS
def is_known_spdx_exception(token: str) -> bool:
    return token.lower() in EXCEPTIONS

Try / catch

from packaging.licenses import canonicalize_license_expression, InvalidLicenseExpression
try:
    canonicalize_license_expression(expr)
except InvalidLicenseExpression as e:
    if 'Unknown license exception' in str(e):
        # strip the WITH clause if you don't actually need the exception
        expr = expr.split(' WITH ')[0]
        canonicalize_license_expression(expr)
    raise

Prevention

When it happens

Trigger: Expressions like `GPL-2.0-only WITH Foo` (Foo not an SPDX exception), `Apache-2.0 WITH 389-exception-typo`, or `MIT WITH Classpath` (incomplete exception name).

Common situations: Typos in exception names; inventing exception identifiers that aren't in the SPDX exception list; using a license name where an exception is expected; outdated packaging vendoring an older SPDX list missing newer exceptions.

Related errors


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/551fb2a19df735d2.json. Report an issue: GitHub.