Repository navigation
ci: separate release builds from signing and publishing - #59046
Conversation
There was a problem hiding this comment.
I reviewed this by reading it; I couldn't run it. The release pipeline needs GitHub runners, the release environment, Apple/Azure signing, npm and R2. The update-server tests pass (17/17). The type-check scripts don't run in this environment.
The build/sign split, the manifest digest pinning and the package allowlist in publish-npm look solid. Three concerns, details inline:
- Every push to
devnow waits on thereleaseenvironment. Before, only versioned releases and beta did. Each push's sign-cli and npm/CLI publish jobs hit that gate at different stages, and the update server now accepts tokens only fromrelease. If that environment has required reviewers, one dev preview could need several separate approvals. The description doesn't mention this. - The update server allows
latestfor anyworkflow_dispatchondev, including dispatches without a bump or version, so it doesn't fully matchplan's rule. - The release commit's changelog includes today's date. A new dispatch after a failed release, on a later UTC day, can't reuse the existing tag and fails.
Public API: no package APIs change. Script.release is removed from the internal @opencode/script, and the update server's publish endpoint now requires the environment: release and event_name OIDC claims.
.github/workflows/publish.yml:654: Ondevpushes (dry_run=false) this and the other sign/publish jobs now run inrelease. The base workflow said dev previews don't wait for approval, and the update server now accepts onlyenvironment: releasetokens, so dev can't simply drop it. Ifreleasehas required reviewers, each dev push stops at several gates reached at different times (sign-cli-macos/windows, then publish-npm, then publish-cli). Is that intended? If not, a separate environment for dev previews, accepted by the update server for thedevchannel only, would keeplatestbehind approval without gating every push..github/workflows/publish.yml:164: The entry's date comes fromdate -uat run time. If a versioned release fails after the tag is pushed and is dispatched again on a later UTC day, the tree changes and the check below stops with "Tag already exists with different contents". So the advertised tag reuse only works within the same day, or when "Re-run failed jobs" keepssource's output. Taking the date from the existing tag's commit, or leaving it out of the tree check, would make retries reliable.
Related: #56297
| if (ref === "refs/heads/dev") return ["dev", "latest"] | ||
| if (ref === "refs/heads/beta") return ["beta"] | ||
| // Pushes to dev ship dev builds. Only a manual dispatch of a versioned release may ship to latest. | ||
| export function allowedChannels(claims: Pick<GitHubClaims, "ref" | "event_name">) { |
There was a problem hiding this comment.
This allows latest for any workflow_dispatch on dev, including dispatches with no bump or version, which plan treats as the dev channel. The description says the server enforces the same rule as plan, but only plan checks for the versioned inputs. If the server should enforce it on its own, it needs something that identifies a versioned run, for example a separate environment for versioned releases.
There was a problem hiding this comment.
The new commit only moves the logic in packages/ui/script/pack.ts into an exported pack() to satisfy lint. The output is the same: the UI package's exports are all plain strings, and nothing else imports the script. oxlint passes on it. The earlier point, that the update server accepts latest for unversioned dispatches, isn't addressed by this commit. Public API: unchanged by this commit.
There was a problem hiding this comment.
The new commit fixes a real problem: the redirect created an empty SHA256SUMS before find ran, so each manifest listed itself with a stale hash, and sha256sum -c --strict in the sign/publish jobs would have failed. I reproduced the failure with the old command and checked that the new one passes both the checksum check and the file-set diff. The remaining manifest writers name their files explicitly or use sha256sum -- *, so they never had the problem. Public API: unchanged by this commit.
There was a problem hiding this comment.
The new commit fixes a real problem. Every library tarball sets publishConfig: {"access": "public"}, so the earlier has("publishConfig") check would have made publish-npm reject all of them. I ran the new filter on every workspace package.json with a publishConfig: all pass. A publishConfig with another key such as registry is still rejected, and a malformed value makes jq fail, which stops the job before publishing. Public API: unchanged by this commit.
There was a problem hiding this comment.
The new commit updates the desktop packaging test to match the config change that removed signing. It now checks that mac.sign, mac.notarize and win.signtoolOptions are unset, where it used to expect a sign function, which the earlier removal would have broken. bun test electron-builder.config.test.ts passes (7/7). Public API: unchanged by this commit.
…the release environment
There was a problem hiding this comment.
The refactor holds together. Sign and publish jobs now check out only .github/release/, at the workflow's own commit, and run those scripts and the two local download/upload actions. With /.github/ owned by @anomalyco/ci-owners, that keeps the boundary the description sets out. The checksum and digest checks behave as before, and publish-npm.sh still publishes only the signed macOS and Windows CLI packages.
I ran bun run pack in packages/client through the new shared @opencode/script/pack helper: the tarball's exports point at built .js/.d.ts files, and they are present. The imports mapping for core, util and plugin matches the old per-package scripts. The desktop config tests pass (9/9). No callers of the old --target/--app-archive flags or packages/desktop/scripts/ paths are left.
One description detail is out of date: the sign/publish jobs now use local composite actions from .github/release/.
Public API: unchanged by this commit. Only internal build and pack scripts change.
There was a problem hiding this comment.
The new commit is fine. It sends the GH_TOKEN/GITHUB_TOKEN Bearer header on the Bun release lookup, the same way the asset downloads next to it already pick up the token. build-cli passes the read-only github.token, and builds without a token behave as before. Public API: unchanged by this commit.
There was a problem hiding this comment.
The renames are consistent. Every script release.yml calls exists, artifact names and digest keys match the new job names, nothing still points at the old names, and the update server's workflow_ref and the attestation --signer-workflow both now say release.yml. The Docker build job gets the setup-bun it needs for bun run pack:docker. services/update tests pass (17/17).
One thing to handle outside the code before the first real run: npm trusted publishing and the deployed update server both key on the workflow filename (inline).
Public API: unchanged by these commits.
| @@ -0,0 +1,678 @@ | |||
| name: release | |||
There was a problem hiding this comment.
Renaming the workflow file affects two things outside the repo:
- npm: trusted publishing is configured per workflow filename. Each
@opencode/*package (and the CLI platform packages) has to be switched frompublish.ymltorelease.ymlin npm, or the firstnpm publishhere fails the OIDC exchange. - Update server: the deployed worker accepts only one
workflow_ref. If it's deployed before this merges, the currentpublish.ymlruns ondevstop registering; if after, the firstrelease.ymlrun gets 403s. Its deploy needs to land together with the merge.
There was a problem hiding this comment.
The new commit is fine for testing this PR. release-dry-run.yml differs from release.yml only in its name and triggers, and a pull_request event always maps to the dry-run pr channel, so it can't publish. release.yml no longer runs on pull requests. Remember to delete the copy before merging. Public API: unchanged by this commit.
| # version on dev reaches latest. | ||
| # .github/release/ and this file are the security boundary for releases. | ||
|
|
||
| # TEMPORARY copy of release.yml for this PR's dry run: GitHub does not start runs for the release.yml path on |
There was a problem hiding this comment.
If GitHub won't start release.yml runs because that path once belonged to publish.yml, check the repo's Actions settings: if the old release.yml workflow is disabled or stale there, pushes to dev and release dispatches may not start it after merge either until it's re-enabled. Worth confirming before relying on it, along with deleting this file before merging.
# Conflicts: # .github/workflows/publish.yml # packages/cli/script/build.ts # packages/desktop/electron-builder.config.ts # packages/desktop/electron.vite.config.ts # packages/desktop/script/beta-notes.test.ts # packages/desktop/script/beta-notes.ts # packages/desktop/script/beta-visual.ts # packages/desktop/script/upload.ts # packages/desktop/script/utils.ts # packages/desktop/scripts/publish.ts # script/sign-windows.ps1
There was a problem hiding this comment.
The merge from dev is resolved correctly. Signing stays out of the build in the desktop config and utilities, dev's beta-notes and i18n imports now use script/, and dev's new models-catalog job, catalog smoke checks and beta settings are in release.yml build jobs with no secrets. The desktop config and beta-notes tests pass (55/55).
One leftover from the merge: nix/desktop.nix still hands its CLI over through OPENCODE_CLI_DIST, which this PR no longer reads (inline).
Public API: unchanged by these commits.
nix/desktop.nix:98:script/build.tsno longer readsOPENCODE_CLI_DIST; it only bundles a CLI found inpackages/cli/dist. The Nix build prepares its CLI here, sobuild.tsignores it: with noOPENCODE_CHANNELset it resolves todevand tries to download the published dev CLI, which needs network access. Point this atpackages/cli/dist(or havebuild.tsaccept an explicit CLI directory again) so the Nix desktop bundles the Nix-built CLI.
|
estou solicitando extorno da compra. por favor responda meus email, e me devolva meu dinheiro. seus servico esta suspenso. e pelo jeito muitas pessoas estao com o mesmo problema.! quero extornar a compra! |
1 similar comment
|
estou solicitando extorno da compra. por favor responda meus email, e me devolva meu dinheiro. seus servico esta suspenso. e pelo jeito muitas pessoas estao com o mesmo problema.! quero extornar a compra! |
Supersedes #59028: this PR also includes its changes (every secret-using job scoped to the
releaseenvironment, CODEOWNERS, least-privilege permissions, and removal of the VS Code publish workflow).What changes
The release workflow is now
.github/workflows/release.yml(name: release), renamed frompublish.yml. Building is fully separated from signing and publishing:plandecides the version, channel and npm dist-tag from the event, the dispatch inputs andgithub.refonly (.github/release/plan-release.sh).build-*jobs run repository code through one interface,bun run buildandbun run pack. They have no environment, no secrets and noid-token.sign-*andpublish-*jobs run in environmentrelease. They check out only.github/release/(a sparse checkout of that path atgithub.sha), never the rest of the repo, and never runbun installor any repo script. They download artifacts, verify them, treat them as data, and run the scripts in.github/release/with pinned actions and runner tools.latestand the stable desktop feed can only be reached throughworkflow_dispatchwithbump/versionondev.planenforces this, and so does the update server (see below)..github/release/andrelease.ymlare the security boundary for releases. Both are under/.github/, which CODEOWNERS assigns to@anomalyco/ci-owners.Layout
All of it is plain bash with no repo dependencies.
release.ymlwent from 1,913 lines (the first version of this PR) to 675.publish.ymlondevis 677 lines and ran repo scripts with secrets.One build convention
Every package that produces a release artifact exposes the same scripts with the same meaning, and reads the same environment:
OPENCODE_VERSION,OPENCODE_CHANNEL,OPENCODE_TARGETandOPENCODE_RELEASE_NOTES. Pack outputs land at the repository root indist/<kind>/.bun run buildbun run packdist/(JS + types)dist/npm/<name>-<version>.tgzvia the shared@opencode/script/packclipackages/cli/dist/cli-<target>/for all 12 targets, orOPENCODE_TARGETdist/npm/(wrapper + 12 platform packages, pluscli-nodewhen built) anddist/archives/opencode-<target>.tar.gzcli(Node SEA)bun run build:node→packages/cli/dist/node/cli-node-<target>/packabovecli(Docker)bun run pack:docker→dist/docker/opencode-image.tar(OCI, amd64 + arm64)desktopout/plusresources/(icons, metainfo, bundled CLI)dist/desktop/: macOS.apptarball, Windows unpackedapp/, Linux AppImage/deb/rpm; withOPENCODE_DESKTOP_APP, the NSIS installer around a signed appbun run pack: every npm package at the build versionThe root
bun run packloopsbun run packover the packages. Every artifact gets itsSHA256SUMSfrom one script,.github/release/write-checksums.sh, via theuploadaction.Job graph
Job names are
plan,tag, thenbuild-<target>,sign-<target>andpublish-<target>. Artifact names are<stage>-<target>:build-cli,build-node-<target>,build-npm,build-archives,build-docker,build-desktop-<target>,build-desktop-windows-installer-<target>;sign-macos,sign-windows,sign-desktop-windows-<arch>,sign-desktop-windows-installer;publish-winget.Targets use one scheme everywhere:
darwin-arm64,windows-x64,linux-arm64-musl, …devdev--tag dev, R2, update feeddevbetabetabetalatestdevwithbump/versionv*tag + release commit, npmlatest, R2,latestfeeds, Homebrew, AUR, GHCR, GitHub Release, WinGet manifests, version PRSecrets and repo code per job
planpermissions: {})tagGITHUB_TOKENcontents: writebuild-*GITHUB_TOKENsign-macosreleaseAPPLE_CERTIFICATE,_PASSWORD,APPLE_API_KEY_PATH,APPLE_API_KEY,APPLE_API_ISSUERsign-windows,sign-desktop-windows-installerreleaseAZURE_*publish-attestationsid-token,attestations: writepublish-npmreleasepublish-cli,publish-desktopreleaseCLOUDFLARE_API_TOKEN, update-server OIDCpublish-dockerreleaseGITHUB_TOKENpackages: writepublish-homebrewreleaseOPENCODE_APP_SECRET(token scoped tohomebrew-tap), update-server OIDCpublish-aurreleaseAUR_KEY, update-server OIDCpublish-github-releasereleaseGITHUB_TOKENcontents: writepublish-wingetpublish-version-prGITHUB_TOKENcontents/pull-requests: writeRules:
SHA256SUMSand must hold exactly the listed files.sign-*must also match the manifest digest that its sign job reported in a job output, so an artifact replaced after that job finished is rejected.planand the scripts' allowlists; each tarball'spackage.jsonis read withjqas data.evaled.Renamed and unified
publish.yml→release.yml(name: release).source→tag;attest→publish-attestations;release-pr→publish-version-pr;winget-manifests→publish-winget;package-desktop-windows→build-desktop-windows-installer;sign-desktop-windows-installerkeeps its name;build-node-app-archiveis folded intobuild-node;sign-cli-macos+sign-desktop-macos→sign-macos;sign-cli-windows+sign-desktop-windows→sign-windows.scripts/→script/, the repo-wide convention (20 packages to 1);prepare.tsandprebuild.tsfold intoscript/build.ts;package,package:mac,package:winandpackage:linuxscripts are replaced bypack;copy-bundles.ts, a Tauri leftover, is deleted;x86_64-apple-darwin…) becomedarwin-x64…;OPENCODE_CLI_TARGET→OPENCODE_TARGET;OPENCODE_CLI_DISTis gone:buildusespackages/cli/distwhen it holds the target;OPENCODE_VERSIONthroughextraMetadatainstead of rewritingpackage.json.--target=→OPENCODE_TARGET, with the samelinux-x64values as desktop and Node;--app-archive=→OPENCODE_APP_ARCHIVE;build-nodedefaults todist/node.script/build.ts(codegen) →script/generate.ts, andscript/build-package.ts→script/build.ts, sobuildrunsbuild.tsandgeneraterunsgenerate.ts.@opencode/script/pack. Every tarball'spackage.jsonand file list is byte-identical to before, apart from the newpackscript entry; I checked by packing all 14 with the old and new scripts and diffing.OPENCODE_VERSION,OPENCODE_CHANNEL,OPENCODE_TARGET,OPENCODE_RELEASE_NOTES, plusOPENCODE_NPM_TAGandOPENCODE_DRY_RUNin.github/release/.What CI on this PR proves
Run 40244223496 passed every job in the graph. The only skips are by design: attestations and the version PR don't run in a dry run. All other checks are green as well.
Real builds:
--prepackaged.Sign and publish jobs, without secrets:
codesign --verify --deep --strictpasses, and the dmg/zip/.app.tar.gzare built from the signed apps;npm publish --dry-run;desktop*.ymlfeeds are rendered;skopeoinspects the image, and the GitHub Release assets are assembled.Not provable without secrets:
v*tag push and the immutable release publish.All of these fail closed.
Why the dry run uses a copy. It runs from a temporary copy,
.github/workflows/release-dry-run.yml, which has the same jobs, only apull_requesttrigger, and is to be deleted before merge. GitHub never starts runs for the path.github/workflows/release.ymlin this repo. A minimal probe workflow at that path didn't trigger on push either, while the identical file namedprobe-x.ymldid.release.ymlwas the original name ofpublish.ymluntil June 2025 (f0dbe40522), so GitHub most likely still holds a stale workflow record for that path. See settings item 1.Deleted
script/{publish,version,update-artifact,publish-winget}.ts,script/sign-windows.ps1,packages/cli/script/{publish-aur,publish-homebrew}.ts,packages/desktop/scripts/{publish,prepare,prebuild,copy-bundles}.ts, all 14packages/*/script/publish.ts(nowpack.tson the shared helper),packages/ui/script/publish.ts,.github/workflows/{notify-discord,publish-vscode}.yml,.github/publish-python-sdk.yml, and the three entitlements plists, which moved to.github/release/entitlements/.Also removed:
bun i -g opencode-ai, the Tauri leftovers, and the unused secret references;GH_TOKEN(App token) being passed into the build.Settings Dax must change (none were changed)
release.ymlmust be able to run. Before merging, check under Actions → All workflows (signed in) for a disabled or stale "release" workflow at.github/workflows/release.ymland enable it, or confirm that a push todevstarts it right after merge. If it won't start, the fallback is to keep the filenamepublish.ymlwithname: release.@opencode/*package (14 libraries,@opencode/cli, 12@opencode/cli-*, 6@opencode/cli-node*), set workflowrelease.ymland environmentrelease. Then require 2FA and disallow tokens.services/updatenow requiresworkflow_ref == anomalyco/opencode/.github/workflows/release.yml@<ref>andenvironment == release, and allowslatestonly from aworkflow_dispatchondev. Deploy it viaproductionright after merging. It rejectspublish.ymltokens, so if it ships earlier, dev builds can't register.repo:anomalyco/opencode:environment:release. If any trust is keyed onjob_workflow_ref, point it atrelease.yml.APPLE_*secrets into thereleaseenvironment, then rotate them.AZURE_*,CLOUDFLARE_API_TOKEN,OPENCODE_APP_SECRETandAUR_KEYintorelease.BRENDAN_GITHUB_TOKEN,PORTER_*andDISCORD_ISSUES_WEBHOOK_URL.OPENCODE_API_KEYifbot-docs-localeand v1 still need it.v*tag rulesets: add GitHub Actions as a bypass actor on both overlapping rulesets (or merge them), and removeopencode-agentfrom the bypass.publish-version-pr.opencode-agentApp: it's now only used forhomebrew-tap. Drop itssecrets,organization_secretsandworkflowswrite, and rotate its key.v1, and code-owner review ondevso changes to.github/need ci-owners.Rollout
release-dry-run.yml, then merge.services/update.devpush (npmdev, R2,devfeed) and abetapush (desktop signing,betafeed)../script/release.ts patch.Residual risks
devis still built and signed with our identity, and ships to the channel it was built for..github/is the boundary. Anyone who can merge todevwithout code-owner review, and org admins, can change what runs inrelease.release, becausepublish-attestationsand the dry run holdid-tokenoutside it.skopeo,awsandghcome from the runner image.Deliberately left inconsistent
packages/console, the docs, the update feeds, and the Homebrew/AUR formulas use them:opencode-desktop-linux-x86_64.AppImage,-amd64.deb,-aarch64.rpm;opencode2-node-*.prodvslatest) are untouched, as you asked.check-*,bot-*,triage-*) are untouched; you're handling them separately.tagsits outside the build/sign/publish pattern, as you named it: it creates the version commit and tag before anything builds.build,build:node,pack:docker) because the Bun binaries, the Node SEA and the image are separate artifacts.script/build.tsfor tsc-only packages.ai,plugin,plugin-browseranduibuild withtscdirectly. The interface isbun run build, so I didn't add a wrapper file.desktop/script/bench-startup.tsandprofile-by-source.tsmoved as pure renames, so they still mentionpackage:winandscripts/. Editing them would makelint:changeddemand fixes for about 70 pre-existing lint errors.lint:changednow skips exact moves (-M100%).packno longer runstypecheck:tsc(some old publish scripts did,uialso ran its tests). CI'scheckandcheck-testcover that.Added by the opencode bot (AI-generated).
Related: #56297