DeepDeck release operations

August 28, 2026 · View on GitHub

DeepDeck uses GitHub Releases for the version record and manual downloads, and a Cloudflare R2 custom domain as electron-updater's production generic feed. The first production matrix is macOS arm64 and x64. Windows x64 packaging is configured but is not enabled in the release matrix until Windows signing credentials and acceptance testing are available.

One-time external setup

Create a protected GitHub Environment named production-release. Require reviewers if desired and add these secrets:

SecretPurpose
CSC_LINKDeveloper ID Application certificate, normally a base64 PKCS#12 value or a secured URL
CSC_KEY_PASSWORDPKCS#12 password
APPLE_API_KEYContents of the App Store Connect API .p8 key; the workflow materializes it only in the runner's temporary directory
APPLE_API_KEY_IDApp Store Connect API key ID
APPLE_API_ISSUERApp Store Connect API issuer ID
VL_NATIVE_KEYVibeLoft Native v4 write key injected into the Electron main-process telemetry configuration
R2_ACCOUNT_IDCloudflare account ID
R2_ACCESS_KEY_IDR2 S3 access key ID
R2_SECRET_ACCESS_KEYR2 S3 secret access key
R2_BUCKETTarget update bucket name
UPDATE_BASE_URLHTTPS custom-domain origin, for example https://updates.example.com

The Apple certificate must be a valid Developer ID Application certificate and the API key must be authorized for notarization. Give the R2 token Object Read & Write access to only the update bucket. No GitHub, Apple, R2, or Cloudflare credential is embedded in the app.

In Cloudflare:

  1. Create the R2 bucket and attach the hostname in R2 → Bucket → Settings → Custom Domains.
  2. Use that custom hostname for UPDATE_BASE_URL. A public r2.dev hostname is rejected by the production workflow.
  3. Preserve byte-range requests. The workflow requires 206 and an exact Content-Range before it promotes a release.
  4. Do not place Cloudflare Access, a JS Challenge, CAPTCHA, or an interactive WAF rule on the update hostname. electron-updater cannot complete browser challenges.
  5. Respect the uploaded object headers. Versioned DMG, ZIP, EXE, and blockmap objects use public, max-age=31536000, immutable; latest*.yml uses no-cache, no-store, must-revalidate.
  6. Keep old installers and blockmaps. Differential updates may need the prior version's blockmap.

Cloudflare's official references are R2 custom domains, R2 and cache, and S3 API compatibility.

For future Windows publication, provision either an EV/OV signing certificate through WIN_CSC_LINK and WIN_CSC_KEY_PASSWORD, or Azure Trusted Signing credentials. Do not enable the Windows release job until a signed NSIS install/update cycle passes on a clean Windows x64 machine.

Feed layout

electron-builder generates the names referenced by each architecture's metadata. A typical v1.0.0 layout is:

stable/
  darwin/
    arm64/
      latest-mac.yml
      DeepDeck-1.0.0-mac-arm64.dmg
      DeepDeck-1.0.0-mac-arm64.zip
      DeepDeck-1.0.0-mac-arm64.zip.blockmap
    x64/
      latest-mac.yml
      DeepDeck-1.0.0-mac-x64.dmg
      DeepDeck-1.0.0-mac-x64.zip
      DeepDeck-1.0.0-mac-x64.zip.blockmap

Additional blockmaps generated by electron-builder are retained and uploaded. GitHub Release receives both architectures' immutable assets, architecture-qualified copies of latest-mac.yml, release-manifest.json, and SHA256SUMS.

Local acceptance

Use Node 24 and pnpm 12.0.0, then run:

pnpm install --frozen-lockfile
pnpm codex-connect:install
pnpm harness:install
pnpm harness:build
pnpm package:local

package:local builds plugins and desktop code, prepares the independent runtime, creates an unsigned DeepDeck.app, and verifies:

  • DeepDeck bundle, executable, helper, icon, version, and permanent app ID;
  • no Electron-branded resource files or helper metadata;
  • the updater and native identity code exist in app.asar;
  • the bundled Harness contains no checkout-bound symlinks;
  • bundled Node can start the complete Web profile with an empty system PATH and return an HTTP page;
  • the pinned Open Computer Use package, launcher, DeepDeck app-agent proxy, license, and platform-native executable are present and runnable;
  • the nested macOS helper plist, Mach-O constants, and code-signing identifier all use com.jo32.deepdeck.cu-helper, its DeepDeck-specific fallback socket is present, and an actual proxied MCP handshake returns all nine Computer Use tools.

pnpm start:packaged performs this branded package flow and launches DeepDeck. The normal pnpm start command uses the raw-Electron development path so routine launches do not rebuild and verify a complete application package; pnpm start:raw remains an explicit alias for that path.

Open Computer Use runtime updates

.github/workflows/update-computer-use.yml checks npm daily and on manual dispatch for the latest stable open-computer-use release. It validates the package identity, MIT license, upstream repository, registry tarball origin, and published SHA-512 integrity metadata before refreshing the exact dependency and pnpm-lock.yaml. An update is proposed as a pull request and must pass the normal DeepDeck CI before merge. The packaging hook also checks the audited fixed-width identifier and socket occurrence counts for the pinned native binary; a changed upstream layout fails closed and requires an explicit identity rewrite review before the update can ship.

Production release jobs deliberately continue to install with --frozen-lockfile. They never resolve a floating latest during signing, so the reviewed package and checksum in the release tag are exactly the bytes that all architecture jobs build. Run pnpm computer-use:update to perform the same version refresh locally.

Production release

  1. Bump apps/desktop/package.json to the new SemVer version and merge the fully tested change.
  2. Create and push an exact matching tag such as v1.0.1. A manual dispatch must also name an existing matching tag.
  3. Approve the production-release Environment when prompted.
  4. Keep the GitHub Release in draft until the workflow completes all publication stages.

The workflow enforces this order:

  1. validate tag, app version, fixed identity, HTTPS feed, signing policy, and custom domain;
  2. install with frozen lockfiles, build Harness, run check/test/build;
  3. copy the pinned Open Computer Use launcher and native runtime into the independent client runtime;
  4. rewrite the nested helper to com.jo32.deepdeck.cu-helper, build natively on Apple Silicon and Intel runners, sign it with the same Team ID as DeepDeck, verify the signing identifier, notarize, staple, and assess each app;
  5. verify the packaged independent runtime and update configuration;
  6. assemble one manifest and verify electron-builder SHA-512/size metadata;
  7. create the draft GitHub Release and upload the same build outputs plus SHA-256 sums;
  8. upload R2 versioned objects with If-None-Match: * so a published version cannot be overwritten;
  9. verify R2 metadata, public HEAD, Content-Length, full SHA-256, and one-byte Range responses;
  10. upload and re-read each latest-mac.yml as the final update switch;
  11. publish the GitHub Release.

Build provenance is attached with GitHub's current actions/attest. Its additional artifact-metadata: write permission is required by the official action; all other jobs retain read-only repository permissions until the publish job.

R2 has no multi-object transaction across the arm64 and x64 latest-mac.yml keys. The workflow verifies every immutable object before changing either key and then promotes the two metadata objects consecutively. If strict cross-architecture atomicity becomes necessary, add a Worker or a single manifest indirection; it is not needed for electron-updater's architecture-isolated feeds.

Rollback and recovery

Never overwrite a versioned R2 key and never point latest-mac.yml at a lower version. If v1.2.3 is faulty, fix the issue and release v1.2.4. Existing versioned files and blockmaps remain available.

If the workflow fails before the latest-metadata step, clients continue seeing the prior release. The draft GitHub Release and identical immutable R2 objects may be inspected; an identical pre-existing R2 object is accepted only when its size, cache policy, and stored SHA-256 match. A conflicting object stops the release. If GitHub asset upload partially completed, remove the draft and restart rather than clobbering assets.

If publishing the GitHub Release fails after R2 promotion, fix the GitHub permission/state and publish the existing draft without rebuilding. Do not rerun a different build for the same version.

End-to-end update acceptance

Before calling the channel production-ready, perform a real two-version test using the configured custom domain:

  1. install signed vN on a clean Mac with no source tree, pnpm, or system Node;
  2. publish signed vN+1 through the workflow;
  3. confirm the sidebar reports vN+1 and shows real download progress;
  4. confirm the download uses the ZIP blockmap/Range path where applicable;
  5. confirm Harness stops, quitAndInstall(false, true) installs and relaunches, and the About panel reports vN+1;
  6. compare GitHub and R2 SHA-256 values with SHA256SUMS.

Stable is the only enabled channel. A future beta prefix can be added without changing stable. electron-updater's stagingPercentage can later support 10% → 30% → 100% rollout, but it should be introduced only with an operational promotion/abort procedure.