ci: add PyPI publish workflow with trusted publishing#601
Conversation
.github/workflows/publish.yml
Outdated
| python .github/update_versions.py bump --bump-type "$key" | ||
| ;; | ||
| patch-rc|minor-rc|major-rc) | ||
| bump="${key%-rc}" | ||
| python .github/update_versions.py bump --bump-type "$bump" | ||
| python .github/update_versions.py bump --pre rc | ||
| ;; | ||
| next-rc) | ||
| python .github/update_versions.py bump --pre rc | ||
| ;; | ||
| promote) | ||
| python .github/update_versions.py bump --bump-type release |
There was a problem hiding this comment.
🔴 Click command invoked with spurious bump positional argument causes UsageError
The script defines a single click.command("bump") (not a click.group), but every invocation in the workflow passes bump as a positional argument (e.g., python .github/update_versions.py bump --bump-type patch). Since the click command has no positional parameters—only --pre and --bump-type options—click treats bump as an unexpected extra argument and raises UsageError: Got unexpected extra argument (bump). This was verified by running the script. All 5 invocations in .github/workflows/publish.yml:72-83 are affected, meaning the entire version-bumping workflow is broken and no release PR can be created.
Reproduction
import click, sys
@click.command('bump')
@click.option('--bump-type', default='patch')
def bump(bump_type): print(f'ok: {bump_type}')
sys.argv = ['script.py', 'bump', '--bump-type', 'patch']
bump() # => UsageError: Got unexpected extra argument (bump)Was this helpful? React with 👍 or 👎 to provide feedback.
| version=$(python -c " | ||
| import re, pathlib | ||
| m = re.search(r'__version__\s*=\s*[\"'\''](.*?)[\"'\'']', pathlib.Path('${version_file}').read_text()) | ||
| print(m.group(1)) | ||
| ") |
There was a problem hiding this comment.
🔴 Python syntax error in 'Read new version' step due to incorrect shell quoting of single quotes inside double-quoted string
The python -c "..." command in the "Read new version" step uses the '\'' idiom to embed single quotes, but this technique only works in single-quoted bash strings (break out of single quote, add escaped single quote, re-enter single quote). Here the code is inside a double-quoted bash string (the python -c "..." argument), where single quotes are literal characters — so '\'' passes through as the literal four characters '\''.
This causes the Python raw string r'...' to terminate prematurely at the ' character after [", and Python then encounters a \ which triggers SyntaxError: unexpected character after line continuation character. I confirmed this by running the exact shell command locally.
This completely blocks the release PR creation workflow — the version can never be read, so the step fails and no release PR is created.
Reproduction and error output
$ version_file="livekit-rtc/livekit/rtc/version.py"
$ python3 -c "
import re, pathlib
m = re.search(r'__version__\s*=\s*[\"'\''](.*?)[\"'\'']', pathlib.Path('${version_file}').read_text())
print(m.group(1))
"
File "<string>", line 3
m = re.search(r'__version__\s*=\s*["'\''](.*?)["'\'']', ...)
^
SyntaxError: unexpected character after line continuation characterPrompt for agents
The python -c command in the 'Read new version' step (publish.yml lines 114-118) has broken shell quoting. The single-quote escaping idiom '\'' only works inside single-quoted bash strings, not inside the double-quoted python -c argument.
The simplest fix is to avoid the problematic quoting entirely. Since write_new_version in update_versions.py always writes double quotes around the version string, you can match only double quotes:
version=$(python -c "
import re, pathlib
m = re.search(r'__version__\s*=\s*\"(.*?)\"', pathlib.Path('${version_file}').read_text())
print(m.group(1))
")
Alternatively, you could use a heredoc to avoid the quoting issues entirely:
version=$(python3 << PYEOF
import re, pathlib
m = re.search(r'__version__\s*=\s*["\x27](.*?)["\x27]', pathlib.Path('${version_file}').read_text())
print(m.group(1))
PYEOF
)
Or, even better, add a --read-version flag to update_versions.py and call it directly, avoiding the fragile inline Python altogether.
Was this helpful? React with 👍 or 👎 to provide feedback.
| new_text = re.sub( | ||
| r'"livekit-protocol>=[\w.\-]+,', | ||
| f'"livekit-protocol>={new_protocol_version},', | ||
| old_text, |
There was a problem hiding this comment.
🟡 Major version bump of livekit-protocol creates unsatisfiable dependency constraint in livekit-api
update_api_protocol_dependency only updates the lower bound of the version constraint in livekit-api/pyproject.toml but leaves the upper bound untouched. When doing a major bump (e.g., 1.1.4 → 2.0.0), the existing dependency "livekit-protocol>=1.1.1,<2.0.0" at livekit-api/pyproject.toml:33 becomes "livekit-protocol>=2.0.0,<2.0.0" — an impossible constraint that no version can satisfy. This would produce a broken release PR for livekit-api.
Trace through the code
The regex r'"livekit-protocol>=[\w.\-]+,' matches "livekit-protocol>=1.1.1, (up to and including the first comma). The replacement inserts the new version >=2.0.0,, but <2.0.0 remains from the original text, yielding >=2.0.0,<2.0.0.
Prompt for agents
The update_api_protocol_dependency function in .github/update_versions.py only updates the lower bound (>=) of the livekit-protocol dependency in livekit-api/pyproject.toml but does not update the upper bound (<). When the new protocol version crosses a major boundary (e.g., 2.0.0), the constraint becomes >=2.0.0,<2.0.0 which is unsatisfiable.
The fix should also update the upper bound to the next major version. For example, if new_protocol_version is 2.0.0, the upper bound should become <3.0.0. One approach: parse the new version with packaging.version.Version, compute the next major (major+1), and use a regex that replaces both the lower and upper bounds. Alternatively, expand the regex to capture the full constraint and rebuild it.
Relevant code: update_api_protocol_dependency function at .github/update_versions.py:61-74, and the current pyproject.toml constraint at livekit-api/pyproject.toml:33.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
publish.ymlworkflow — single dropdown to pick version bump type, creates a release PR, and on merge builds + publishes all 3 packages to PyPI via trusted publishing (OIDC)release-gate.yml— blocks merge on release PRs until 2 approvals and verifies author is github-actions[bot]update_versions.py— bumps versions for livekit, livekit-api, and livekit-protocol, updates livekit-api's dependency on livekit-protocolSetup needed
pypiGitHub environment (Settings → Environments)Release gateas required status check onmainbranch protectionlivekit, Repo:python-sdks, Workflow:publish.yml, Environment:pypi