CI: sign and CDN-upload binaries on release branches only
Errata — 2026-09-10
This body has been modified from the original. Everything below this block is the original text, unchanged. This note corrects it.
Fix design item (2) rests on a false premise. It says "the binary is already produced in-workspace by the packaging job before publish — split 'build + persist binary to workspace' from 'trigger publish'." There is nothing to split.
create-and-trigger-packaging-artifactshas five steps, and none of them builds a binary:maybe_skip_binary_jobs restore_cached_workspace check-if-binary-exists setup_should_persist_artifacts trigger-publish-binary-pipelineThe linux binary is built inside the separate
cypress-publish-binaryproject, andget-published-artifactsdownloads it. The in-repobuild-and-package-binarycommand is used only bycreate-build-artifacts, which is the Windows/darwin/arm64 job, not the linux-x64 chain. So repointing the develop binary tests at a workspace build means adding a linux binary build to the develop workflow, not re-plumbing an existing one. That is materially more work than this body describes.Fix design item (4) asks for something CircleCI cannot do. A
context:list is static per workflow job entry; there is no conditional-context mechanism. The saving is also misattributed — thewindowsworkflow'swhen:(@main.yml:617-628) excludes plaindeveloppushes, so Windows signing is not a develop-merge cost in the first place.The Mechanism section's scope is incomplete. The binary chain is not develop-only.
pull-request.yml:355-400carries the samecreate-and-trigger-packaging-artifacts→wait-for-binary-publish→get-published-artifactschain, filtered only against fork branches, so it runs on every internal PR. Gating only the@main.ymlcopy leaves that path untouched, and the publish trigger fires there wheneverCIRCLE_TOKENis set.Ordering hazard not addressed: filtering
create-and-trigger-packaging-artifactsoff develop makeswait-for-binary-publishimmediately available, soget-published-artifactsthen runs with no~/triggered_pipeline.jsonto read.Constraints (1)–(4) as originally written are sound; constraint (4) on
force-persist-artifactsremains the right review blocker.
What would you like?
Stop producing signed, CDN-uploaded release-candidate binaries on every develop merge. Signing and upload move to release/* branches — plus one first-class requirement: ad-hoc signing/publishing must stay triggerable from CircleCI's Trigger Pipeline UI, from any branch, with no config edits. The existing force-persist-artifacts pipeline parameter already does exactly this today (it forces the full workflow, packaging and publish included, on any branch), so the requirement is that the new gating preserves it: every publish-leg gate must be release branch OR force-persist-artifacts. Develop merges keep building binaries and keep every binary test — the test-binary-* suite runs against unsigned workspace builds instead of published artifacts.
Why is this needed?
Every develop merge currently runs the full packaging-and-publish chain: build artifacts, trigger the publish-binary pipeline (signing + CDN upload), wait, download the published artifacts, then run ~15 binary test jobs against them. The signing and publishing legs of that chain cost an estimated ~0.9M credits/month, and their output — a signed develop binary at a CDN URL — is a pre-release convenience, not a correctness requirement. The binary tests validate the product; the signature validates distribution, and we distribute from release branches.
Other
This is action 5 of the config-level cost work in the internal CI cost report (2026-08-31, "The CI Wedge"). It is policy-gated, and this issue should settle the policy before implementation: internal consumers of develop install URLs (anyone doing CYPRESS_INSTALL_BINARY=<develop CDN url>) move to unsigned workspace builds. If specific workflows genuinely need a signed develop binary, Trigger Pipeline with force-persist-artifacts=true covers them on demand — that path exists today and must survive this change unweakened.
Effort is marked high in the report: the binary test jobs are wired to get-published-artifacts, so pointing them at workspace builds is a real re-plumbing of the develop workflow, not a filter flip.
Out of scope here: any changes to how the publish-binary pipeline itself builds — this issue only changes when it is triggered.
Overlap note: this action shares Windows jobs with the nightly-platform-frequency work; the report's savings stack counts each credit once.
Attn Agents:
Mechanism. The develop chain lives in the linux-x64 workflow of .circleci/src/pipeline/workflows/@main.yml: create-and-trigger-packaging-artifacts (:342-348, contexts test-runner:upload, test-runner:build-binary, publish-binary) → wait-for-binary-publish (approval, :349-352) → get-published-artifacts (:353-358) → ~15 downstream jobs (test-npm-module-on-minimum-node-version, test-types-cypress-and-jest, test-full-typescript-project, test-binary-against-kitchensink*, test-binary-against-recipes*, test-binary-against-staging, test-binary-as-specific-user ×2, binary-system-tests, yarn-pnp-preprocessor-system-test, svelte-webpack-system-test), most gated by *mainBuildFilters (:365) which includes develop. The publish trigger itself: .circleci/src/pipeline/commands/trigger-publish-binary-pipeline.yml fires a pipeline in the separate cypress-io/cypress-publish-binary project (:56-70), where signing/CDN upload run and bill. Windows signing: windows-create-build-artifacts (@main.yml:674-685) carries the test-runner:sign-windows-binary context; the analogous linux-arm64/darwin create-*-artifacts jobs are in their platform workflows. Per-push parameter publish-binary-branch is declared at .circleci/config.yml:16-18.
Evidence. ~0.9M credits/mo (estimated: publish-binary pipeline runs + signing/upload job costs × develop merge rate, 30-day Insights window ending 2026-08-31). The binary tests are not part of the saving — they continue. The saving is the signing/notarization/upload legs and the duplicate artifact handling around them.
Fix design. (1) Gate create-and-trigger-packaging-artifacts's publish trigger (and the platform create-*-artifacts signing contexts) on pipeline.git.branch matches /^release\/.../ or pipeline.git.branch matches /^electron\/.../ or pipeline.parameters.force-persist-artifacts == true — expression-based job filters, same pattern as *mainBuildFilters (@main.yml:365), which already includes the force-persist-artifacts clause; develop merges skip the cypress-publish-binary trigger entirely, while a UI trigger with force-persist-artifacts=true runs the full signed-publish chain from any branch, exactly as today. (2) Repoint the develop binary-test chain at the unsigned build: the binary is already produced in-workspace by the packaging job before publish — split "build + persist binary to workspace" from "trigger publish", keep the first on develop, and change get-published-artifacts → an attach_workspace-based equivalent for develop (branch-conditional job or a parameterized source). (3) wait-for-binary-publish (manual approval) becomes release-only along with the publish leg — this also removes a develop merge sitting on a human approval. (4) Windows: drop test-runner:sign-windows-binary from windows-create-build-artifacts for non-release runs; windows-test-binary-against-kitchensink-chrome consumes the unsigned artifact.
Constraints. (1) Policy decision first: develop CDN install URLs stop being signed — get sign-off from whoever consumes pre-release binaries (docs reference them for bug-report reproduction) before landing. (2) macOS notarization/stapling can change binary behavior (Gatekeeper) — the release pipeline must still exercise the signed binary path, so keep the full publish chain on release/* untouched and treat release branches as the signed-path coverage. (3) npm-release on develop (@main.yml:334-341) publishes the npm package pre-release and must keep working — confirm whether it consumes published binary URLs (the CDN link embedded in the pre-release package comment via scripts/add-install-comment.js) and, if so, either keep a lighter unsigned upload for the npm pre-release path or gate that too as part of the policy. This is the likeliest hidden coupling; resolve it before coding. (4) force-persist-artifacts is the ad-hoc UI trigger for signed publishes and is load-bearing in two places: the workflow-level full-workflow-filters (@main.yml:13, so the workflow runs at all on an arbitrary branch) and *mainBuildFilters (@main.yml:365, so the publish-adjacent jobs run). Every new publish-leg gate must include the force-persist-artifacts clause, or the UI path silently dies — treat any gate lacking it as a review blocker. Note the develop nuance: on develop the workflow always runs, so force-persist-artifacts=true on a develop UI trigger is what distinguishes "publish this one" from a normal filtered merge.
Fallback. If the policy stalls on unsigned develop URLs: keep publish on develop but drop it to the same nightly schedule as the platform workflows (~30/mo), saving most of the 0.9M while preserving a daily signed binary. Accepted cost: pre-release binaries lag develop by up to a day.
Verification plan. (a) develop merge: no cypress-publish-binary pipeline triggered; all test-binary-* and binary-system-tests jobs green against the workspace build; npm-release succeeds and its install comment resolves to a working install path. (b) release/* push: publish pipeline triggered, signed artifacts on CDN, binary tests green against published artifacts. (c) Trigger Pipeline from the CircleCI UI with force-persist-artifacts=true — once on develop, once on an arbitrary feature branch — and confirm both produce a signed, published binary end-to-end (publish pipeline triggered, artifact on CDN); this check is mandatory before merge. (d) Verify a fresh CYPRESS_INSTALL_BINARY install from the develop artifact output on Linux and Windows.
Interim mitigation. None; current behavior continues until the policy call is made. Do not remove the manual publish path before the release-branch flow is proven.
Source: cypress-io/cypress