Release Management Pro

A release receipt
for every deploy.

renmy release validate proves a payload, locally and then inside the target org, and writes an immutable receipt. renmy release deploy accepts only that receipt. Approval sits between them, and the two halves are free to run on different machines on different days, because the evidence travels as a file rather than as a claim about a worktree.

Anatomy of a receipt

A receipt is a JSON file written under .nimbus/releases/ beside the content-addressed bundle it describes. It records what was proven, against which bytes, in which org, at which moment. Signing is a one-time setup (renmy release keygen creates a key and trusts it in the project); once it is on, a receipt carries a signed attestation of who validated it, so the record is tamper-evident rather than merely written down. A profile can set requireSigned to refuse unsigned receipts outright.

It is deliberately a portable artifact, not a local cache. Receipts carry no org aliases, no credentials, and no source, so uploading one between CI jobs leaks nothing. When a downloaded receipt is passed by path, renmy locks, reads, and updates that file beside its sibling bundle instead of quietly creating a second local copy.

renmy release verify re-hashes the bundle and checks the signature offline, on any machine, with no org and no network. renmy release export writes the whole receipt set plus the signer roster into one self-verifying evidence document for an archive an auditor can re-check years later, without renmy runtime, using verify-export.

bash
# Prove a payload and write the receipt
renmy release validate --target-org staging

# Only what is new or differs from the org —
# the gates still run the full suite
renmy release validate --target-org staging --changed

# Read the record back
renmy release list
renmy release get rel_20260720T141342_0397df68b0 --json

# Offline: re-hash the bundle, check the signature
renmy release verify rel_20260720T141342_0397df68b0

# Archive the whole evidence set
renmy release export --org-kind production \
  --since 2026-07-01 --output evidence-2026Q3.json
Bundle digest

A content-address over the exact uncompressed bundle that validate staged. At deploy time the stored bundle is re-hashed against it. A byte that moved is a release integrity violation (exit code 25) rather than a warning.

Git revision

The commit the bundle was staged from, so the evidence points back at reviewable source. A profile can require a clean worktree; an uncommitted tree needs --allow-dirty and that fact is recorded on the receipt itself.

Org fingerprint

A one-way fingerprint of the target org, never the alias and never a credential. Deploying the receipt at a different org fails closed. It is also why the deploy step still needs its own --target-org or --release-profile.

Gate results

What the local gates actually measured against that bundle: tests, line coverage, mutation score where a threshold is set, Salesforce Code Analyzer violation counts with the rule policy that produced them, and permission-seam counts.

Validation job ID

The Salesforce check-only validation job that checked the same payload in the target org. For production targets that job is what a later quick deploy reuses, so the org-side proof travels with the receipt.

Quick-deploy expiry

The exact moment the validated job stops being quick-deployable. maxReceiptAge caps how old any receipt may be at deploy time and defaults to the 10-day Salesforce quick-deploy window, because an aged bundle would deploy against a drifted org.

Immutability is enforced at deploy time. A receipt whose source changed, whose validation expired, whose target org does not match, or whose toolchain is incompatible is rejected and you revalidate. There is no --force, no --skip-verify, and no configuration key that reintroduces one. An override flag that exists is an override flag that gets used at 5pm on a Friday, so the flag does not exist.

Validate, approve, and deploy across two machines

A one-shot gated deploy is the right shape when one person decides and ships in the same minute. Regulated and multi-environment teams do not work that way: the proof is produced by CI, the decision is made by a human hours later, and the deployment is executed somewhere else entirely. The release workflow is built for that gap rather than around it.

  1. validate

    Validate on whichever runner is free

    renmy release validate snapshots the selected source into a content-addressed bundle, runs the local gates against that bundle, then has Salesforce check the same bytes in the target org. With no selector it defaults to the packageDirectories in sfdx-project.json, exactly like sf project deploy start. It writes the receipt and the bundle under .nimbus/releases/ and stops. Nothing is deployed.

  2. approve

    Approve, by a human or your platform gate

    The receipt and bundle move as an ordinary pipeline artifact through whatever approval your platform already enforces: GitHub required reviewers, a GitLab manual job, an Azure Environment check, or a person reading the release notes. renmy's --confirm-production is not that approval. It is the non-interactive acknowledgement that the gate was already passed, because a CI runner has no terminal to type a confirmation into. A production profile can additionally require a counter-signature, so the identity that validated a release cannot be the identity that ships it.

  3. deploy

    Deploy the validated payload

    renmy release deploy re-checks the receipt before it does anything: org fingerprint, bundle digest, receipt age, toolchain version policy, and whether baseline components drifted in the org since validation. Where Salesforce still supports it, the validated org-side job is quick-deployed within the 10-day window, with no source retransmitted. Otherwise the exact stored bundle is re-sent. It reruns no gates and restages no source, which is precisely why the bytes cannot change between the proof and the deploy.

Each failure mode gets its own exit code, so a pipeline can react without parsing prose: 21 a local gate, 22 Salesforce rejected the payload, 23 the deploy failed, 25 a release integrity violation, 27 baseline components drifted in the org after validation, 29 a required counter-signature is missing.

Because the receipt stores only a one-way org fingerprint and never the alias, the deploy step is told its target explicitly. After a successful validate the printed Next: line is copy-paste runnable and already carries it.

bash
# Machine A — CI, on merge
renmy release validate --release-profile production

# ── approval happens here ──

# Machine B — the gated deploy job
renmy release deploy \
  --receipt .nimbus/releases/rel_....json \
  --release-profile production \
  --confirm-production

# Both halves in one step, when that is honest
renmy release run --release-profile staging

# Read-only: what would reconciling the org
# to your source actually change?
renmy release plan --target-org staging
After the deploy. renmy release promote carries the identical bytes to the next org, re-checked there, as a new receipt chained to its origin. renmy release rollback restores what a deploy overwrote, from the snapshot taken automatically just before it ran. One limit is worth knowing before an incident: components the bad deploy added stay in the org, because a snapshot of prior content holds nothing for something that did not previously exist. Removing them is a destructive change and stays a separate, explicit step.

When a Salesforce deployment is stuck in Pending

Salesforce runs one deployment at a time per org and does not show you the queue. So a deploy that is politely waiting its turn looks exactly like a deploy that is hung, and a Pending job the scheduler silently dropped looks exactly like one that is about to start. Without the queue, the only available move is to wait longer, and the only available diagnosis is a guess.

renmy release status reads the org's deployment queue directly: each job's position, live component and test counters, and recent history correlated with your local receipts. A job that is genuinely stuck (Pending beyond a threshold, never started, with nothing running that would explain the wait) is called out as stuck, with the remedy. The threshold is nimbus.release.stuckPendingAfter, three minutes by default.

renmy release watch follows a Salesforce job ID (0Af…) or a receipt until it reaches a terminal state and mirrors the outcome in its exit code. Watching is read-only: it never cancels and never resubmits, so it is safe to point at anyone's deployment, including the one that is blocking yours.

renmy release requeue is the remedy. It cancels the stuck Pending job and resubmits the same validated bundle, which is the manoeuvre that unsticks it, because a job that never started has executed nothing org-side. It takes a receipt, never a bare job ID, and refuses anything that is not provably the stuck case: the org must match the receipt's fingerprint, and the job must be Pending and never started. A running job is left alone; a finished one asks you to revalidate.

bash
# Is it queued, running, or actually stuck?
renmy release status --target-org production

# Stable JSON for a scripted gate
renmy release status --json --limit 20

# Follow a job — or a receipt — to its outcome.
# Read-only: safe on anyone's deployment.
renmy release watch 0Af5g00000XXXXXCAA
renmy release watch rel_20260720T141342_0397df68b0

# Cancel the stuck Pending job and resubmit
# the byte-identical validated bundle
renmy release requeue rel_20260720T141342_0397df68b0

# Exit code 26 means "submitted, outcome
# unconfirmed" — the deploy may still be running
renmy release watch <job>
Most of the time you will never run requeue. While renmy is attending its own job it polls the org and streams progress. When that job sits Pending past nimbus.release.autoRequeueAfter (five minutes, up to nimbus.release.maxRequeues attempts), it cancels and resubmits the identical bundle by itself, recording every recovery attempt on the receipt. No re-confirmation is asked, because nothing ran and the payload cannot drift. Deployments renmy did not create are diagnosed but never touched: cancelling a colleague's deploy is a human decision. renmy release status is read-only and deliberately not Pro-gated, because knowing your deploy is stuck is safety, and the safe default is never paywalled.

The pipeline shape

The pipeline shape is the workflow shape: a validate job that ends by uploading .nimbus/releases/, your platform's own environment protection, and a deploy job that downloads the same artifact into a fresh runner. The receipt's own verification is the safety net for that hand-off. Nothing about the artifact needs to be trusted, because it is re-checked before a single byte is sent.

One detail the deploy job is easy to get wrong: it still needs the project skeleton on disk. The Salesforce CLI deploys in project context, so the checkout that runs renmy release deploy must contain sfdx-project.json and every path its packageDirectories declares. An empty force-app/ is fine. The directory only has to exist.

A pipeline on GitHub Actions, GitLab CI/CD, Azure DevOps or a plain shell runner reads the same ci release profile and pins the Salesforce CLI with renmy toolchain sf install --version, so the runner provably reproduces the configured toolchain. The release-in-CI reference covers the profile keys, the secrets, and the full exit-code contract.

yaml
# GitLab CI/CD, condensed
validate:
  script:
    - renmy toolchain sf install --version "$SF_VERSION"
    - renmy release validate --release-profile ci
  artifacts:
    paths: [".nimbus/releases/"]   # receipt + bundle

deploy:
  needs: [validate]
  when: manual                     # ← the human gate
  environment:
    name: production               # protected environment
  script:
    - renmy release deploy
        --receipt "$(ls .nimbus/releases/rel_*.json)"
        --release-profile ci
        --confirm-production

The evidence layer beside your promotion tooling

renmy release is the evidence layer, and only the evidence layer. It does not manage package versions, dependency-ordered installs, scratch-org pools, branch promotion models, or data seeding. Those belong to the package manager and promotion pipeline you already run, and they are good at them. Point renmy release at the bundle your pipeline assembles and it adds proof around the deploy rather than opinions about the pipeline that built it.

That division is the design, not a roadmap gap. A promotion tool answers “what moves next, and in what order?” A receipt answers a different question, “what exactly was proven, against which bytes, in which org, by whom, and is that still true right now?” Teams need both answers, and they are better answered by two tools that stay in their lane than by one tool with a view on everything.

renmy deploy is the free starting point.

renmy deploy (local gates, Salesforce validation, and a local receipt in one command) is Free, and it is the right shape until a human approval has to sit in the middle. The release workflow on this page is Pro: currently free, no card required.