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
- Use the exact SPDX exception identifier from https://spdx.org/licenses/exceptions-index.html
- If you need a custom exception, express it as a separate LicenseRef or drop the WITH clause
- 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
- Cross-check exception IDs against the SPDX exceptions index
- Upgrade packaging to refresh the SPDX list when new exceptions are added
- Use exact hyphenated identifiers (e.g. Classpath-exception-2.0)
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
- Invalid licenseref: {final_token!r}
- Invalid license expression: {raw_license_expression!r}
- Unknown license: {final_token!r}
- Invalid requirement: {req_as_string!r}: {exc}
- Invalid requirement: {req_string!r}: {exc}
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/551fb2a19df735d2.json.
Report an issue: GitHub.