[PR #127] [MERGED] ci(cursor-review): scheduled bump workflow for the Cursor CLI version pin (BE-5870) #136

Closed
opened 2026-10-26 22:36:37 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/Comfy-Org/github-workflows/pull/127
Author: @mattmillerai
Created: 8/4/2026
Status: ✅ Merged
Merged: 8/4/2026
Merged by: @mattmillerai

Base: main ← Head: matt/be-5870-bump-cursor-cli-pin


📝 Commits (1)

  • e2752c1 ci(cursor-review): scheduled bump workflow for the Cursor CLI version pin (BE-5870)

📊 Changes

2 files changed (+378 additions, -0 deletions)

View changed files

➕ .github/workflows/bump-cursor-cli-pin.yml (+372 -0)
📝 AGENTS.md (+6 -0)

📄 Description

ELI-5

Last week we stopped installing the Cursor CLI from a script that could change under us, and started installing one exact, checksummed version instead. That's safer, but nobody moves that version — so left alone the review panel would run an ever-older CLI forever. This adds a robot that checks once a week: if Cursor shipped a newer CLI, it downloads it, fingerprints it, asks a second source whether it got the same fingerprint, installs it to prove it actually works, and then opens a small two-line PR asking a human to approve moving the pin. Merging that PR automatically rolls the pin out to every repo that uses our review workflow.

What this adds

.github/workflows/bump-cursor-cli-pin.yml — a scheduled (Mondays 14:00 UTC) + workflow_dispatch job that:

  1. Fetches https://cursor.com/install as data (there is deliberately no bash "$installer" anywhere in the file) and extracts the shipped version from its downloads.cursor.com/lab/<version> URL. An empty extraction is a hard ::error:: + exit 1, not a silent no-op — an installer-format change is exactly the vendor drift BE-5869 wants early warning about.
  2. Reads the current pin with an anchored grep of ^ CURSOR_CLI_VERSION: ; equal → logs and exits 0 (no token minted, no branch touched).
  3. Downloads linux/x64/agent-cli-package.tar.gz for the new version and computes its sha256.
  4. Cross-checks against nixpkgs (pkgs/by-name/cu/cursor-cli/package.nix), best-effort: same version + different hash → hard fail (a CDN-tamper signal is the whole point of the pin); nixpkgs on an older version → log "not yet updated, proceeding TOFU" and continue; fetch failure → log and continue. It is never a gate — nixpkgs lags releases.
  5. Smoke-tests the proposed pin by running the repo's own .github/cursor-review/install-cursor-cli.sh (see judgment calls).
  6. Rewrites the two env lines with anchored sed -i, failing if either substitution changed nothing.
  7. Mints the Cloud Code Bot token, force-resets the stable branch ci/bump-cursor-cli-pin, and opens-or-updates a PR to main titled ci(cursor-review): bump Cursor CLI pin to <version>.

Structure, guards and comment style follow bump-cursor-review-callers.yml: contents: read at workflow level, if: github.ref == 'refs/heads/main' job guard, concurrency: {group: bump-cursor-cli-pin, cancel-in-progress: false} around the stable branch, actions/create-github-app-token@d72941d… # v1.12.0 with app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.CLOUD_CODE_BOT_PRIVATE_KEY }} — verified byte-identical references to the bump fleet's.

Also adds a 6-line entry to the AGENTS.md workflow catalog (the file was already over the aspirational 150-line target on main; the checker still passes — warnings only, no new class of warning).

Verification — the live pipeline, run for real

actionlint (which runs shellcheck over every run: block) is clean on the new file. The repo's four suites — cursor-review, agents-md-integrity, groom, bump-callers (+ shellcheck -x) — all pass, and check_agents_md.py --root . passes.

Beyond that I extracted every run: block from the workflow verbatim and executed them in ubuntu:24.04 (--platform linux/amd64) against the live installer, the live CDN artifact and the live nixpkgs expression, with the pin seeded stale so the changed-path runs end to end. Transcript, abridged:

===== seed a stale pin =====
147:  CURSOR_CLI_VERSION: 2020.01.01-stale00
150:  CURSOR_CLI_SHA256: 0000000000000000000000000000000000000000000000000000000000000000
===== step 01 discover =====
pinned: 2020.01.01-stale00
latest: 2026.07.23-e383d2b
===== step 02 digest =====
sha256: 702ad595213bee5df0268be9f80a19f29fcceaa2a42fc55e39f2b5199051f0c4
sri=sha256-cCrVlSE77l3wJovp+AoZ8p/M6qKkL8VeOfK1GZBR8MQ=
===== step 03 nixpkgs =====
nixpkgs agrees: sha256-cCrVlSE77l3wJovp+AoZ8p/M6qKkL8VeOfK1GZBR8MQ=
===== step 03b nixpkgs MISMATCH must be fatal =====
::error::nixpkgs pins 2026.07.23-e383d2b as sha256-cCrV… but this run computed sha256-BOGUS…
OK: mismatch exited non-zero
===== step 03c stale-nixpkgs (different version) must NOT be fatal =====
nixpkgs is still on 2026.07.23-e383d2b, not yet updated to 9999.99.99-notreal — proceeding TOFU.
===== step 04 smoke-test / 04c bad digest =====
OK: bad digest rejected
===== step 05 move the pin =====
-  CURSOR_CLI_VERSION: 2020.01.01-stale00
+  CURSOR_CLI_VERSION: 2026.07.23-e383d2b
-  CURSOR_CLI_SHA256: 0000…0000
+  CURSOR_CLI_SHA256: 702ad595213bee5df0268be9f80a19f29fcceaa2a42fc55e39f2b5199051f0c4
===== step 05b re-run must fail (substitution changes nothing) =====
::error::The CURSOR_CLI_VERSION substitution changed nothing.
===== step 01 no-op path (pin already current) =====
Pin is already current — nothing to do.
ALL SIM CHECKS PASSED

Two things that transcript establishes beyond "the extraction parses today's format": the digest the workflow computes for the currently-shipped version is byte-identical to the pin BE-5869 landed (702ad595…), and the SRI it derives is byte-identical to what nixpkgs independently recorded (sha256-cCrVlSE7…) — so the cross-check is genuinely comparing two independent fetches of the same URL, not comparing a value to itself. I also ran the discover step against a URL with no downloads.cursor.com/lab/ string and confirmed it fails loudly rather than no-opping:

::error::Could not extract a version from https://example.com/ — its format has changed.
OK: empty extraction failed loudly

The two paths the sim can't cover are the App-token mint and the real git push / gh pr create (stubbed, with their arguments asserted). Those are the parts copied verbatim from the bump fleet.

Judgment calls (all deviations from the ticket, flagged)

  • ref: main on the checkout (ticket said only "checkout … persist-credentials: false"). Without it, a re-run of an older scheduled run checks out that run's now-stale github.sha, and the force-push would publish a branch that reverts everything merged since. The bump fleet solves the same hazard by aborting on a stale tip; that would be wrong here — this job has no successor run to do the work, so a skip loses the week. Resolving the ref at run time keeps the bump correct instead of skipping it.
  • Plain --retry (not --retry-all-errors) on the artifact download. The ticket said "same curl hygiene"; I followed install-cursor-cli.sh's reasoning instead — a 404 there means the discovered version has no linux/x64 artifact, which is permanent, and retrying turns a clear error into an opaque one. The installer curl keeps --retry-all-errors exactly as specified.
  • Added a smoke-test step (not in the ticket) that runs the repo's own install-cursor-cli.sh against the proposed pin. A digest tells a reviewer nothing about whether the archive layout changed or whether the binary self-reports the pinned version — and a bad pin merged here breaks every caller's review. It reuses the single source of truth rather than forking a check, and runs after the nixpkgs cross-check on purpose, since it executes the downloaded binary.
  • No owner: on the token; added permission-contents: write + permission-pull-requests: write. Unlike the caller-bumping fleet this only writes to this repo, so the default repo-scoped installation token is both sufficient and narrower. groom.yml's build_pr job already pushes a branch and opens a PR here with the same app, so the installation is known to cover this repo; I confirmed both permission-* inputs exist in the pinned v1.12.0 action manifest.
  • The bump PR is not labelled cursor-review. A weekly two-line pin change doesn't warrant an 8-cell model panel; the human reviewer is the trust anchor and is the point. Easy to add later if wanted.
  • Non-fatal cross-check outcomes are surfaced, not swallowed — not-yet-available / fetch-failed / not-available all land in the PR body's table so the reviewer knows exactly how much corroboration this particular digest got.

Risk

The riskiest line is the sed -i pair that rewrites cursor-review.yml, since a bad rewrite would break the review workflow for every consumer. It is guarded three ways: each key must match exactly one ^ KEY: line before any edit (a restructured file aborts), each substitution must actually change the file, and the interpolated values are validated first (^[0-9A-Za-z][0-9A-Za-z._-]{0,63}$ for the version, ^[0-9a-f]{64}$ for the digest) so nothing from a remote file reaches sed unshaped. And the output is a PR, never a push to main.

Nothing here auto-merges, and no capability is denied or removed — the change is purely additive (one new workflow file + a catalog entry).

Closes BE-5870.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/Comfy-Org/github-workflows/pull/127 **Author:** [@mattmillerai](https://github.com/mattmillerai) **Created:** 8/4/2026 **Status:** ✅ Merged **Merged:** 8/4/2026 **Merged by:** [@mattmillerai](https://github.com/mattmillerai) **Base:** `main` ← **Head:** `matt/be-5870-bump-cursor-cli-pin` --- ### 📝 Commits (1) - [`e2752c1`](https://github.com/Comfy-Org/github-workflows/commit/e2752c185c842186cc763b43af0e92ae9e385781) ci(cursor-review): scheduled bump workflow for the Cursor CLI version pin (BE-5870) ### 📊 Changes **2 files changed** (+378 additions, -0 deletions) <details> <summary>View changed files</summary> ➕ `.github/workflows/bump-cursor-cli-pin.yml` (+372 -0) 📝 `AGENTS.md` (+6 -0) </details> ### 📄 Description ## ELI-5 Last week we stopped installing the Cursor CLI from a script that could change under us, and started installing one exact, checksummed version instead. That's safer, but nobody moves that version — so left alone the review panel would run an ever-older CLI forever. This adds a robot that checks once a week: if Cursor shipped a newer CLI, it downloads it, fingerprints it, asks a second source whether it got the same fingerprint, installs it to prove it actually works, and then opens a small two-line PR asking a human to approve moving the pin. Merging that PR automatically rolls the pin out to every repo that uses our review workflow. ## What this adds `.github/workflows/bump-cursor-cli-pin.yml` — a scheduled (Mondays 14:00 UTC) + `workflow_dispatch` job that: 1. Fetches `https://cursor.com/install` **as data** (there is deliberately no `bash "$installer"` anywhere in the file) and extracts the shipped version from its `downloads.cursor.com/lab/<version>` URL. An empty extraction is a hard `::error::` + `exit 1`, not a silent no-op — an installer-format change is exactly the vendor drift BE-5869 wants early warning about. 2. Reads the current pin with an anchored grep of `^ CURSOR_CLI_VERSION: `; equal → logs and exits 0 (no token minted, no branch touched). 3. Downloads `linux/x64/agent-cli-package.tar.gz` for the new version and computes its sha256. 4. **Cross-checks against nixpkgs** (`pkgs/by-name/cu/cursor-cli/package.nix`), best-effort: same version + different hash → hard fail (a CDN-tamper signal is the whole point of the pin); nixpkgs on an older version → log "not yet updated, proceeding TOFU" and continue; fetch failure → log and continue. It is never a gate — nixpkgs lags releases. 5. Smoke-tests the proposed pin by running the repo's own `.github/cursor-review/install-cursor-cli.sh` (see judgment calls). 6. Rewrites the two env lines with anchored `sed -i`, failing if either substitution changed nothing. 7. Mints the Cloud Code Bot token, force-resets the stable branch `ci/bump-cursor-cli-pin`, and opens-or-updates a PR to `main` titled `ci(cursor-review): bump Cursor CLI pin to <version>`. Structure, guards and comment style follow `bump-cursor-review-callers.yml`: `contents: read` at workflow level, `if: github.ref == 'refs/heads/main'` job guard, `concurrency: {group: bump-cursor-cli-pin, cancel-in-progress: false}` around the stable branch, `actions/create-github-app-token@d72941d… # v1.12.0` with `app-id: ${{ vars.APP_ID }}` + `private-key: ${{ secrets.CLOUD_CODE_BOT_PRIVATE_KEY }}` — verified byte-identical references to the bump fleet's. Also adds a 6-line entry to the AGENTS.md workflow catalog (the file was already over the aspirational 150-line target on `main`; the checker still passes — warnings only, no new class of warning). ## Verification — the live pipeline, run for real `actionlint` (which runs `shellcheck` over every `run:` block) is clean on the new file. The repo's four suites — cursor-review, agents-md-integrity, groom, bump-callers (+ `shellcheck -x`) — all pass, and `check_agents_md.py --root .` passes. Beyond that I extracted every `run:` block from the workflow verbatim and executed them in `ubuntu:24.04` (`--platform linux/amd64`) against the **live** installer, the **live** CDN artifact and the **live** nixpkgs expression, with the pin seeded stale so the changed-path runs end to end. Transcript, abridged: ``` ===== seed a stale pin ===== 147: CURSOR_CLI_VERSION: 2020.01.01-stale00 150: CURSOR_CLI_SHA256: 0000000000000000000000000000000000000000000000000000000000000000 ===== step 01 discover ===== pinned: 2020.01.01-stale00 latest: 2026.07.23-e383d2b ===== step 02 digest ===== sha256: 702ad595213bee5df0268be9f80a19f29fcceaa2a42fc55e39f2b5199051f0c4 sri=sha256-cCrVlSE77l3wJovp+AoZ8p/M6qKkL8VeOfK1GZBR8MQ= ===== step 03 nixpkgs ===== nixpkgs agrees: sha256-cCrVlSE77l3wJovp+AoZ8p/M6qKkL8VeOfK1GZBR8MQ= ===== step 03b nixpkgs MISMATCH must be fatal ===== ::error::nixpkgs pins 2026.07.23-e383d2b as sha256-cCrV… but this run computed sha256-BOGUS… OK: mismatch exited non-zero ===== step 03c stale-nixpkgs (different version) must NOT be fatal ===== nixpkgs is still on 2026.07.23-e383d2b, not yet updated to 9999.99.99-notreal — proceeding TOFU. ===== step 04 smoke-test / 04c bad digest ===== OK: bad digest rejected ===== step 05 move the pin ===== - CURSOR_CLI_VERSION: 2020.01.01-stale00 + CURSOR_CLI_VERSION: 2026.07.23-e383d2b - CURSOR_CLI_SHA256: 0000…0000 + CURSOR_CLI_SHA256: 702ad595213bee5df0268be9f80a19f29fcceaa2a42fc55e39f2b5199051f0c4 ===== step 05b re-run must fail (substitution changes nothing) ===== ::error::The CURSOR_CLI_VERSION substitution changed nothing. ===== step 01 no-op path (pin already current) ===== Pin is already current — nothing to do. ALL SIM CHECKS PASSED ``` Two things that transcript establishes beyond "the extraction parses today's format": the digest the workflow computes for the currently-shipped version is **byte-identical to the pin BE-5869 landed** (`702ad595…`), and the SRI it derives is **byte-identical to what nixpkgs independently recorded** (`sha256-cCrVlSE7…`) — so the cross-check is genuinely comparing two independent fetches of the same URL, not comparing a value to itself. I also ran the discover step against a URL with no `downloads.cursor.com/lab/` string and confirmed it fails loudly rather than no-opping: ``` ::error::Could not extract a version from https://example.com/ — its format has changed. OK: empty extraction failed loudly ``` The two paths the sim can't cover are the App-token mint and the real `git push` / `gh pr create` (stubbed, with their arguments asserted). Those are the parts copied verbatim from the bump fleet. ## Judgment calls (all deviations from the ticket, flagged) - **`ref: main` on the checkout** (ticket said only "checkout … `persist-credentials: false`"). Without it, a re-run of an older scheduled run checks out that run's now-stale `github.sha`, and the force-push would publish a branch that *reverts everything merged since*. The bump fleet solves the same hazard by aborting on a stale tip; that would be wrong here — this job has no successor run to do the work, so a skip loses the week. Resolving the ref at run time keeps the bump correct instead of skipping it. - **Plain `--retry` (not `--retry-all-errors`) on the artifact download.** The ticket said "same curl hygiene"; I followed `install-cursor-cli.sh`'s reasoning instead — a 404 there means the discovered version has no linux/x64 artifact, which is permanent, and retrying turns a clear error into an opaque one. The installer curl keeps `--retry-all-errors` exactly as specified. - **Added a smoke-test step** (not in the ticket) that runs the repo's own `install-cursor-cli.sh` against the proposed pin. A digest tells a reviewer nothing about whether the archive layout changed or whether the binary self-reports the pinned version — and a bad pin merged here breaks *every* caller's review. It reuses the single source of truth rather than forking a check, and runs **after** the nixpkgs cross-check on purpose, since it executes the downloaded binary. - **No `owner:` on the token; added `permission-contents: write` + `permission-pull-requests: write`.** Unlike the caller-bumping fleet this only writes to this repo, so the default repo-scoped installation token is both sufficient and narrower. `groom.yml`'s `build_pr` job already pushes a branch and opens a PR here with the same app, so the installation is known to cover this repo; I confirmed both `permission-*` inputs exist in the pinned v1.12.0 action manifest. - **The bump PR is not labelled `cursor-review`.** A weekly two-line pin change doesn't warrant an 8-cell model panel; the human reviewer is the trust anchor and is the point. Easy to add later if wanted. - **Non-fatal cross-check outcomes are surfaced, not swallowed** — `not-yet-available` / `fetch-failed` / `not-available` all land in the PR body's table so the reviewer knows exactly how much corroboration this particular digest got. ## Risk The riskiest line is the `sed -i` pair that rewrites `cursor-review.yml`, since a bad rewrite would break the review workflow for every consumer. It is guarded three ways: each key must match **exactly one** `^ KEY: ` line before any edit (a restructured file aborts), each substitution must actually change the file, and the interpolated values are validated first (`^[0-9A-Za-z][0-9A-Za-z._-]{0,63}$` for the version, `^[0-9a-f]{64}$` for the digest) so nothing from a remote file reaches `sed` unshaped. And the output is a PR, never a push to `main`. Nothing here auto-merges, and no capability is denied or removed — the change is purely additive (one new workflow file + a catalog entry). Closes BE-5870. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-26 22:36:37 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Comfy-Org/github-workflows#136
No description provided.