{"id":"551fb2a19df735d2","repo":"pypa/pip","slug":"unknown-license-exception-token-r","errorCode":null,"errorMessage":"Unknown license exception: {token!r}","messagePattern":"Unknown license exception: (.+?)","errorType":"validation","errorClass":"InvalidLicenseExpression","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/licenses/__init__.py","lineNumber":159,"sourceCode":"\n    python_expression = \" \".join(python_tokens)\n    try:\n        compile(python_expression, \"\", \"eval\")\n    except SyntaxError:\n        message = f\"Invalid license expression: {raw_license_expression!r}\"\n        raise InvalidLicenseExpression(message) from None\n\n    # Take a final pass to check for unknown licenses/exceptions.\n    normalized_tokens = []\n    for token in tokens:\n        if token in {\"or\", \"and\", \"with\", \"(\", \")\"}:\n            normalized_tokens.append(token.upper())\n            continue\n\n        if normalized_tokens and normalized_tokens[-1] == \"WITH\":\n            if token not in EXCEPTIONS:\n                message = f\"Unknown license exception: {token!r}\"\n                raise InvalidLicenseExpression(message)\n\n            normalized_tokens.append(EXCEPTIONS[token][\"id\"])\n        else:\n            if token.endswith(\"+\"):\n                final_token = token[:-1]\n                suffix = \"+\"\n            else:\n                final_token = token\n                suffix = \"\"\n\n            if final_token.startswith(\"licenseref-\"):\n                if not license_ref_allowed.match(final_token):\n                    message = f\"Invalid licenseref: {final_token!r}\"\n                    raise InvalidLicenseExpression(message)\n                normalized_tokens.append(license_refs[final_token] + suffix)\n            else:\n                if final_token not in LICENSES:\n                    message = f\"Unknown license: {final_token!r}\"","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/packaging/licenses/__init__.py#L141-L177","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nlicense = \"GPL-2.0-only WITH ClasspathException\"\n// after\nlicense = \"GPL-2.0-only WITH Classpath-exception-2.0\"","handlingStrategy":"validation","validationCode":"from packaging.licenses._spdx import EXCEPTIONS\ndef validate_with_clause(expr: str) -> None:\n    tokens = expr.upper().split()\n    for i, t in enumerate(tokens):\n        if tokens[i-1] == 'WITH' and t not in EXCEPTIONS:\n            raise ValueError(f'unknown SPDX exception {t!r}')","typeGuard":"from packaging.licenses._spdx import EXCEPTIONS\ndef is_known_spdx_exception(token: str) -> bool:\n    return token.lower() in EXCEPTIONS","tryCatchPattern":"from packaging.licenses import canonicalize_license_expression, InvalidLicenseExpression\ntry:\n    canonicalize_license_expression(expr)\nexcept InvalidLicenseExpression as e:\n    if 'Unknown license exception' in str(e):\n        # strip the WITH clause if you don't actually need the exception\n        expr = expr.split(' WITH ')[0]\n        canonicalize_license_expression(expr)\n    raise","preventionTips":["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)"],"tags":["packaging","licenses","spdx","pep639","validation"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}