From 502e413ebf0fc616b380a2959db36e61a18dc473 Mon Sep 17 00:00:00 2001 From: Jason Lernerman Date: Mon, 24 Aug 2026 14:32:11 -0400 Subject: [PATCH 1/2] ci: reject field names that generated code cannot address MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A field named `from` reached main and only surfaced days later as a red mypy gate in python-sdks, on an unrelated PR: protoc's --pyi_out cannot emit a Python keyword as a parameter name, so it drops the attribute and falls back to an untyped **kwargs. Nothing in this repo's CI could see it — protocol generates Go and JS, never Python. Two checks, because they answer to different authorities. api-linter's core::0140::reserved-words matches a maintained list of Java, JavaScript and Python 3 keywords; the stub check runs the generator and looks for the **kwargs it emits when it gives up on a name. api-linter's default rule set reports 413 problems on one of these files, so the config enables that single rule. Four fields predate the check and are suppressed by path in the config rather than with the in-proto comment, which protoc copies into every language's generated code. --- .api-linter.yaml | 35 ++++++++++++++++++++++ .github/workflows/buildtest.yaml | 50 ++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 .api-linter.yaml diff --git a/.api-linter.yaml b/.api-linter.yaml new file mode 100644 index 000000000..5417291f0 --- /dev/null +++ b/.api-linter.yaml @@ -0,0 +1,35 @@ +# Only AIP-140's reserved-word check is enabled. api-linter's default rule set +# encodes Google's whole API style guide, which these protos do not follow — it +# reports hundreds of problems per file. This one rule is about generated code +# being usable at all: a field whose name is a keyword in Java, JavaScript or +# Python 3 cannot be addressed by name in that language. +- included_paths: ["**"] + disabled_rules: ["core"] + enabled_rules: ["core::0140::reserved-words"] + +# Fields that predate the check. Suppressed here rather than with the in-proto +# `(-- api-linter: ... --)` comment, which protoc copies into the generated code +# of every language. +# +# livekit_rtc.proto TrickleRequest.final +# livekit_models.proto TranscriptionSegment.final +# On the wire since 2021; renaming breaks every SDK. `final` is reserved in +# Java alone, where the generator mangles it. +# +# agent/livekit_agent_session.proto FunctionCall.arguments +# Not a keyword in Python, and JavaScript restricts `arguments` only inside +# a strict-mode function body, not as a property name. +# +# rpc/io.proto SIPCall.from +# Names the SIP From header it carries. rpc/ is generated for Go only, so +# the Python keyword never reaches a stub — renaming is a prerequisite if +# that ever changes. +# +# The Python side of these files stays covered: the stub check in CI runs the +# generator itself, and no suppression reaches it. +- included_paths: + - "livekit_rtc.proto" + - "livekit_models.proto" + - "agent/livekit_agent_session.proto" + - "rpc/io.proto" + disabled_rules: ["core::0140::reserved-words"] diff --git a/.github/workflows/buildtest.yaml b/.github/workflows/buildtest.yaml index 87fe86b7d..29c339d0b 100644 --- a/.github/workflows/buildtest.yaml +++ b/.github/workflows/buildtest.yaml @@ -75,3 +75,53 @@ jobs: ~/.cache/go-build ~/go/pkg/mod key: ${{ steps.go-cache.outputs.cache-primary-key }} + + # A field name that is a keyword in a target language cannot be addressed by + # name in that language. protoc's Python stub generator silently degrades: + # `from` cost python-sdks a red mypy gate days later, on an unrelated PR. + proto-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Set up Go + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: "go.mod" + + - name: Install Protoc + uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # v3.0.0 + with: + version: "35.x" + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install api-linter + run: go install github.com/googleapis/api-linter/cmd/api-linter@v1.72.0 + + # Run from protobufs/: api-linter resolves the files it lints against the + # working directory, and uses -I only for their imports. + - name: Check for reserved words in field names + run: | + set -euo pipefail + go mod download github.com/livekit/psrpc + psrpc=$(go list -m -f '{{.Dir}}' github.com/livekit/psrpc) + test -d "$psrpc/protoc-gen-psrpc/options" + psrpc="$psrpc/protoc-gen-psrpc/options" + cd protobufs + api-linter --config ../.api-linter.yaml --set-exit-status \ + -I . -I "$psrpc" \ + $(find . -name '*.proto' | sed 's|^\./||') + + # The reserved-word rule matches a list; this asks the generator. A stub + # falls back to an untyped **kwargs whenever protoc cannot emit a keyword + # argument for a field, whatever the reason. + - name: Check the Python stubs name every field + run: | + set -euo pipefail + out=$(mktemp -d) + protoc -I=protobufs --pyi_out="$out" \ + protobufs/livekit_*.proto protobufs/agent/*.proto protobufs/logger/*.proto + if grep -rn '\*\*kwargs' "$out"; then + echo "::error::a field name above is unaddressable in Python; rename it" >&2 + exit 1 + fi From 44ed0be7f78f7e94f566274211c4f5a2a0dab979 Mon Sep 17 00:00:00 2001 From: Jason Lernerman Date: Mon, 24 Aug 2026 15:16:13 -0400 Subject: [PATCH 2/2] ci: scope the proto-lint token to contents: read CodeQL flags a job that leaves GITHUB_TOKEN at its default permissions. The job reads the repo and uses the token only for setup-protoc's release downloads. --- .github/workflows/buildtest.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/buildtest.yaml b/.github/workflows/buildtest.yaml index 29c339d0b..04033cdbd 100644 --- a/.github/workflows/buildtest.yaml +++ b/.github/workflows/buildtest.yaml @@ -81,6 +81,9 @@ jobs: # `from` cost python-sdks a red mypy gate days later, on an unrelated PR. proto-lint: runs-on: ubuntu-latest + # setup-protoc uses the token only to raise its release-download rate limit. + permissions: + contents: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1