Build1 publisher3 min readPublished
Protobuf 7.36.0 and ag_ui_strands 0.1.9 ship wheels that SPDX license checks cannot evaluate
Protobuf's 7.36.0 wheel ships its BSD license text but omits the PEP 639 License-Expression field that SPDX-based compliance pipelines evaluate. The gap sits in the built artifact, so a correct LICENSE file in the source repo does not decide what the wheel declares to a scanner.
The Engineer · Build desk
Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction
What happened
- The ag_ui_strands 0.1.9 wheel goes further, with License: None, no License-Expression or License-File field, and no license files anywhere in the archive.
- An earlier protobuf fix, #9441, did not fully resolve the problem, and issue #29440 remains open, according to the post documenting both cases.
- The post's author released licenseproof, a zero-dependency Python CLI that checks wheels, sdists and installed distributions for usable license metadata.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- decision Enabling --require-spdx turns protobuf-style free-text licenses from a warning into a hard failure, a policy that would also reject most of PyPI, since most of it predates PEP 639.
- exposure A package that ships like ag_ui_strands gets flagged as an unknown license, and in enterprise procurement that flag blocks adoption regardless of what the repo says.
- constraint A license audit of the source repo cannot catch this class of defect, because the build include list decides which files reach users.
- constraint A LICENSE_OK verdict only confirms the fields exist, so teams still need a full compliance tool to judge whether the SPDX expression is valid or the terms are compatible.
In a wheel, the license declaration lives in the METADATA file. Protobuf's 7.36.0 wheel puts `License: 3-Clause BSD License` there, with no License-Expression field [1]. That string is free text. The license text itself is present, in a dist-info/LICENSE file, and the missing piece is the machine-readable declaration PEP 639 defines [2]. According to the post that documented the case, a pipeline that keys on SPDX expressions finds nothing in this wheel it can evaluate [3].
The ag_ui_strands case is a different defect. Its MIT license sits at the monorepo root, and the hatch wheel config packages only src/ [6]. A file outside that include path never enters the archive, so the 0.1.9 wheel ships no license text at all [5]. Scanners report it as an unknown license [7]. The licenseproof author wrote that in enterprise procurement, an unknown license amounts to a polite refusal to use the package [8].
Calling both packages properly licensed is accurate only at the repository level. Protobuf's wheel contains the license and declares it in a legacy format. The ag_ui_strands wheel contains neither. licenseproof sorts them the same way. Protobuf's metadata maps to LEGACY_LICENSE, a warning by default that becomes a failure under --require-spdx [1]. The ag_ui_strands wheel is the post's own example of MISSING_LICENSE, a failing verdict [12].
licenseproof is a zero-dependency Python CLI that checks a wheel, an sdist via --sdist, or an installed distribution via --package [9]. Its defaults are well chosen. Legacy metadata only warns because, the author wrote, "most of PyPI predates PEP 639, and failing all of it would be noise" [11]. A `License: UNKNOWN` field counts as absent, since that value is "setuptools' default, not information" [14]. A field reading UNKNOWN is at least honest. Metadata that points at license files missing from the dist fails even when an SPDX expression is present. "A broken reference is worse than no reference," the author wrote [13]. An unparseable result is never a silent pass [14].
I think the warning default is right for a team scanning dependencies it does not control. For a team checking its own release, I would turn on --require-spdx [10]. The field it would fail on is in that team's own build metadata.
The check covers presence. licenseproof confirms a License-Expression field exists, but it does not validate SPDX identifiers or expressions, and it does not interpret license compatibility [15]. A wheel with a malformed expression and intact License-File references would still get LICENSE_OK [2]. The PyPI upload was waiting on rate limits when the post went up, so installing it for now means building from source [16]. Both cases come from the tool's author, who ties each to a public issue: protocolbuffers/protobuf#29440 and ag-ui-protocol/ag-ui#1927 [1] [5].
What to watch
- Whether a protobuf release after 7.36.0 adds a License-Expression field and closes issue #29440.
- Whether ag-ui resolves #1927 by changing the hatch wheel config so the root MIT license reaches the ag_ui_strands wheel.
- Whether licenseproof reaches PyPI and adds semantic validation of SPDX expressions beyond its current presence check.