Repository navigation
Restore TUI publishing and detect undelivered releases #12763
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
d688629
test: catch undelivered TUI releases and unchecked wheel identity
lawrencecchen d7c9365
fix: verify TUI registry delivery and installed wheel identity
lawrencecchen 57e5ff0
test: isolate native TUI publishing from separately deployed worker
lawrencecchen f18f629
fix: scope TUI release verification to shipped native packages
lawrencecchen b35794e
test: keep unpublished experimental Windows package out of default re…
lawrencecchen 8153aa4
fix: make experimental Windows publishing opt-in
lawrencecchen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| name: cmux-tui registry delivery | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| workflow_run: | ||
| workflows: [cmux-tui release binaries] | ||
| types: [completed] | ||
| schedule: | ||
| - cron: "17 */6 * * *" | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: cmux-tui-registry-delivery | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| delivery: | ||
| name: verify registry delivery | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
| steps: | ||
| # Always use the trusted default branch, never workflow_run's source. | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
| with: | ||
| ref: main | ||
| persist-credentials: false | ||
| fetch-depth: 1 | ||
| - name: Check newest stable TUI release on PyPI and npm | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| # The post-build check may still be inside the two-hour delivery grace | ||
| # period. The scheduled check catches forgotten approvals after it. | ||
| run: python3 cmux-tui/scripts/check_release_delivery.py |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| #!/usr/bin/env python3 | ||
| """Check registry delivery independently of the TUI build/publisher dependency chain.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import datetime as dt | ||
| import json | ||
| import os | ||
| import re | ||
| import urllib.request | ||
|
|
||
|
|
||
| TAG_PREFIX = "refs/tags/cmux-tui-v" | ||
| PLATFORMS = ( | ||
| "macosx_11_0_arm64", | ||
| "macosx_10_12_x86_64", | ||
| "manylinux_2_17_aarch64.manylinux2014_aarch64", | ||
| "manylinux_2_17_x86_64.manylinux2014_x86_64", | ||
| "musllinux_1_2_aarch64", | ||
| "musllinux_1_2_x86_64", | ||
| ) | ||
|
|
||
|
|
||
| def version_tuple(value: str) -> tuple[int, ...]: | ||
| if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", value): | ||
| raise ValueError(f"expected stable X.Y.Z version, got {value!r}") | ||
| return tuple(map(int, value.split("."))) | ||
|
|
||
|
|
||
| def newest_stable_tag(refs: list[dict]) -> dict: | ||
| stable = [ | ||
| ref for ref in refs | ||
| if re.fullmatch(re.escape(TAG_PREFIX) + r"[0-9]+\.[0-9]+\.[0-9]+", ref["ref"]) | ||
| ] | ||
| if not stable: | ||
| raise ValueError("no stable cmux-tui release tags found") | ||
| return max(stable, key=lambda ref: version_tuple(ref["ref"][len(TAG_PREFIX):])) | ||
|
|
||
|
|
||
| def wheel_names(version: str) -> set[str]: | ||
| return {f"cmux-{version}-py3-none-{platform}.whl" for platform in PLATFORMS} | ||
|
|
||
|
|
||
| def assess_delivery( | ||
| version: str, | ||
| tagged_at: dt.datetime, | ||
| pypi: dict, | ||
| npm: dict, | ||
| now: dt.datetime, | ||
| grace_seconds: int, | ||
| ) -> dict: | ||
| problems = [] | ||
| for name, published in (("PyPI", pypi["info"]["version"]), ("npm", npm["version"])): | ||
| if version_tuple(published) < version_tuple(version): | ||
| problems.append(f"{name} latest is {published}; expected at least {version}") | ||
| available = { | ||
| wheel["filename"] for wheel in pypi.get("releases", {}).get(version, []) | ||
| if not wheel.get("yanked", False) | ||
| } | ||
| missing = wheel_names(version) - available | ||
| if missing: | ||
| problems.append("missing PyPI wheels: " + ", ".join(sorted(missing))) | ||
| age = (now - tagged_at).total_seconds() | ||
| status = "complete" if not problems else "pending" if age < grace_seconds else "failed" | ||
| return {"version": version, "status": status, "problems": problems} | ||
|
|
||
|
|
||
| def fetch_json(url: str, token: str = ""): | ||
| headers = {"User-Agent": "cmux-tui-release-delivery/1", "Accept": "application/json"} | ||
| if token: | ||
| headers["Authorization"] = f"Bearer {token}" | ||
| request = urllib.request.Request(url, headers=headers) | ||
| with urllib.request.urlopen(request, timeout=30) as response: | ||
| return json.load(response) | ||
|
|
||
|
|
||
| def main() -> int: | ||
| parser = argparse.ArgumentParser(description=__doc__) | ||
| parser.add_argument("--grace-seconds", type=int, default=7200) | ||
| args = parser.parse_args() | ||
| if args.grace_seconds < 0: | ||
| parser.error("--grace-seconds must be nonnegative") | ||
| repository = "manaflow-ai/cmux" | ||
| api = f"https://api.github.com/repos/{repository}" | ||
| token = os.environ.get("GH_TOKEN", "") | ||
| refs = fetch_json(f"{api}/git/matching-refs/tags/cmux-tui-v", token) | ||
| tag = newest_stable_tag(refs) | ||
| version = tag["ref"][len(TAG_PREFIX):] | ||
| obj = tag["object"] | ||
| if obj["type"] == "tag": | ||
| annotation = fetch_json(f"{api}/git/tags/{obj['sha']}", token) | ||
| tagged_at = annotation["tagger"]["date"] | ||
| else: | ||
| commit = fetch_json(f"{api}/commits/{obj['sha']}", token) | ||
| tagged_at = commit["commit"]["committer"]["date"] | ||
| result = assess_delivery( | ||
| version, | ||
| dt.datetime.fromisoformat(tagged_at.replace("Z", "+00:00")), | ||
| fetch_json("https://pypi.org/pypi/cmux/json"), | ||
| fetch_json("https://registry.npmjs.org/cmux/latest"), | ||
| dt.datetime.now(dt.timezone.utc), | ||
| args.grace_seconds, | ||
| ) | ||
| print(json.dumps(result, indent=2)) | ||
| if result["status"] == "complete": | ||
| return 0 | ||
| print( | ||
| "Inspect publisher failures or pending environment approvals at " | ||
| f"https://github.com/{repository}/actions/workflows/tui-publish-pypi.yml and " | ||
| f"https://github.com/{repository}/actions/workflows/tui-publish-npm.yml. " | ||
| "A successful artifact build is not a delivered release." | ||
| ) | ||
| return 1 if result["status"] == "failed" else 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| raise SystemExit(main()) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: manaflow-ai/cmux
Length of output: 612
🤖 get_repo_knowledge executed:
get_repo_knowledge manaflow-ai/cmux /tmp/coderabbit-repo-knowledge/manaflow-ai-cmux-b0f68d40/conventions /tmp/coderabbit-repo-knowledge/manaflow-ai-cmux-b0f68d40/learningsLength of output: 47439
🌐 Web query:
"cmux-tui-build-package.yml" "PYPI_VERSION" "NPM_VERSION"💡 Result:
<search_synthesis>
The file
cmux-tui-build-package.ymlis a GitHub Actions workflow used to build and packagecmux-tui[1]. Within this workflow,PYPI_VERSIONandNPM_VERSIONare environment variables defined to control the versioning of the artifacts produced [1]. Specifically, the workflow defines these variables as follows: - NPM_VERSION: Set directly to the inputversionprovided to the workflow [1]. - PYPI_VERSION: Set to the providedpypi_versionif specified; otherwise, it defaults to the same value as the inputversion[1]. These variables are used during the packaging steps (e.g.,package_npm.py) to specify the version identifier for the generated npm packages and PyPI wheels [1]. Whilecmux-tuiis a component of themanaflow-ai/cmuxproject (a native macOS terminal built on Ghostty), the specific workflowcmux-tui-build-package.ymlidentified in searches appears in an external repository (macintoshUserchg/cmux_windows) [1][2][3].</search_synthesis>
<source_evidence>
Citations:
🌐 Web query:
site:github.com/manaflow-ai/cmux ".github/workflows/cmux-tui-build-package.yml" "PYPI_VERSION" "NPM_VERSION"💡 Result:
<search_synthesis>
In the manaflow-ai/cmux repository, PYPI_VERSION and NPM_VERSION are not static environment variables or workflow constants. Instead, the packaging workflows—specifically.github/workflows/cmux-tui-build-package.yml [1][2][3]—utilize a dynamic
versioninput [4][5]. When triggering a release or publishing a package, a version (e.g., "0.1.0") is provided either via manual workflow_dispatch inputs [4][6] or derived from Git tags (e.g., cmux-tui-vX.Y.Z) [5][1]. The build and publishing pipeline, orchestrated through workflows like tui-publish-npm.yml and tui-publish-pypi.yml, ensures that the npm and PyPI package versions remain synchronized by sharing this same version string throughout the build and release process [5][1][6]. Key points regarding these versions: - Consistency: The npm launcher package, npm platform packages, and PyPI wheels for the cmux TUI all share the same X.Y.Z version for any given release [5]. - Workflow Orchestration: The reusable.github/workflows/cmux-tui-build-package.yml workflow performs the actual packaging and is invoked by higher-level publishing workflows that pass the required version as an input [4][1][2]. - Manual Dispatch: For npm, publishing is manually gated and requires setting confirm_tui_cmux=true to prevent accidental overwrites of the public cmux package [4][5][6]. - Version Derivation: Nightly builds derive their versions automatically from the latest stable release tag by bumping the patch version [5][1].</search_synthesis>
<source_evidence>
Citations:
🌐 Web query:
site:github.com/manaflow-ai/cmux "distribution_version" "CMUX_TUI_EXPECTED_BUILD_IDENTITY" "NPM_VERSION"💡 Result:
<search_synthesis>
In the context of the cmux project, these terms relate to the release management, build identity, and distribution of the cmux-tui (Rust-based terminal multiplexer). distribution_version This refers to the software version (e.g., X.Y.Z) assigned to a release of the cmux-tui. In the codebase, distribution_version is a function in the cmux-tui-core module (defined in release.rs) that typically falls back to the CARGO_PKG_VERSION to identify the current release [1]. This version is independent of the SDK version and is used to coordinate releases across different platforms (npm launcher, platform packages, and PyPI wheels) [2]. CMUX_TUI_EXPECTED_BUILD_IDENTITY This is a component of the cmux-tui release identity system used to ensure compatibility between client and server components [1]. The system uses a ReleaseIdentity struct—which bundles the version, build commit, Ghostty commit, and protocol—to verify that the client and server builds match before allowing attach or control connections [1]. This prevents issues arising from version or build mismatches between the frontend and the backend server [1]. NPM_VERSION While npm version generally refers to the versioning scheme used within the Node.js ecosystem, in the cmux project&
#39;s CI/CD workflows, it is managed through specific GitHub Actions (such as tui-publish-npm.yml) [3]. The project publishes platform-specific npm packages and a main cmux launcher package [2][4]. Recent hardening efforts have included pinning npm to specific versions (e.g., 11.5.1) within stable and nightly release workflows to ensure consistent, secure, and reproducible builds [5].</search_synthesis>
<source_evidence>
Citations:
Use
PYPI_VERSIONfor PyPI wheel validation.The wheel is built and installed with
PYPI_VERSION, but the smoke probe and regression test setdistribution_versionfromNPM_VERSION. Nightly npm and PyPI versions can differ, so the probe can reject a valid PyPI wheel.Update the workflow probe and the test to use
PYPI_VERSION, including the validation environment attests/test_tui_publish_workflow_security.py#L1628-L1628.📍 Affects 2 files
.github/workflows/cmux-tui-build-package.yml#L753-L753(this comment)tests/test_tui_publish_workflow_security.py#L1615-L1615tests/test_tui_publish_workflow_security.py#L1628-L1628🤖 Prompt for AI Agents