Lab 3.5 - Promote to UAT and write the release notes¶
Level: 3 Release Manager
Time: ~35 min
You will: protect a setting UAT keeps for itself, make your first promotion between two major branches, read the deployment actions it carries, and produce the document the business actually reads.
The situation¶
Everything the team built this week is in integration. The business testers work in UAT. Monday
morning they expect to find this week's work there, with a note saying what changed.
A promotion between major branches is not a contributor Pull Request. It carries several stories at once, it may carry deployment actions declared weeks ago by different people, and the org it deploys to has real testers in it.
Before you start¶
- Lab 3.4 finished: US-018 and US-019 merged into
integration -
helios-uatconnected: the scratch org Level 1 created, configured as theuatorg since then - JWT authentication working for
uat(Lab 3.1)
Steps¶
1. See what you are about to ship¶
Open the DevOps Pipeline panel and click the integration node in the diagram. A window opens
on that branch, titled Pull Requests in integration.
Pull Requests (1) is the list that matters: every Pull Request merged into integration
since the last promotion to uat, with who merged it and when. That list is the release. Read
it before you create anything: if a story in it should not go out this week, now is the moment, not
after the deployment.
Deployment Actions (2) is the list of actions those Pull Requests carried, gathered in one
place, and step 4 comes back to it. Tickets beside it is the same again for the stories, each with its
title, read from the backlog the project declares as its ticketing system. A fourth tab, Apex Tests, appears only on a project that sets
enableDeploymentApexTestClasses, and this one does not.
The footer holds the two buttons step 7 uses: (3) generates the notes for what has already been promoted, (4) previews the notes for what has not.
Empty, with a Go Live selector instead?
The window shows what is waiting to be promoted to the next stage. A branch that has no next
stage, meaning its mergeTargets is empty, has nothing to promote, so the panel shows a Go
Live selector (1) and the release notes of a go-live already merged into it instead:
On main that is the normal view and always will be: production is the end of the pipeline.
On integration it means the branch file lost its merge target, which it has had since Lab 1.2.
Click Training: Level 3 > Set up my training environment, which writes it again. It
refreshes the org and the login URL of a file that already exists and leaves the rest alone, so
what you set in Lab 3.1 survives.
2. Protect what UAT keeps for itself¶
One component of the Helios app is meant to be different in every org: the remote site setting
Helios_Warehouse, the address of the warehouse stock system the panel batches are booked with.
Production talks to the real warehouse, UAT to the warehouse's test system. An admin set that address
in UAT by hand, and the repository holds the production one.
See it for yourself: in helios-uat, Setup > Remote Site Settings, open Helios_Warehouse,
Edit, and set Remote Site URL to https://warehouse-test.helios.invalid, the way
the UAT admin did. Save.
Now the promotion. It sends the whole package, remote site setting included, and would put the
production address back in UAT without a word. The overwrite manager is for exactly this:
anything listed in manifest/package-no-overwrite.xml is taken out of the deployment when the target
org already has it, and created when it does not.
The file does not exist yet, and you do not have to write it. In the DevOps Pipeline panel, open the Deployment packages menu, the one that opened Package XML in Lab 1.5, and pick No Overwrite. The package viewer opens on an empty list. Turn Edit mode on (1), then click Add Type (2).
Type RemoteSiteSetting, the name Salesforce gives this kind of component, in Metadata Type API
Name (1), and click Add (2).
The new RemoteSiteSetting row has an Add member button: click it, type Helios_Warehouse,
and Add. The viewer wrote manifest/package-no-overwrite.xml for you, with the shape of
manifest/package.xml: one block per kind of component, its members listed by name. Edit File
opens it as text, if you want to see it.
Then Training: Level 3 > Publish my pipeline configuration, and merge its Pull Request once green, as in Lab 3.1: the list of what must never be overwritten is pipeline configuration, like the rest.
Created where missing, never overwritten where present
A new org, a fresh sandbox for instance, has no Helios_Warehouse yet, and the deployment creates
it from the repository. From then on it belongs to that org. The overwrite manager does not
compare versions: present means protected.
3. Create the promotion Pull Request¶
In the DevOps Pipeline diagram, the arrow from integration to uat carries a + PR chip
(1). Click it: GitHub opens on a new Pull Request from integration into uat, both branches
already filled in.
The chip is there because no Pull Request is open on that arrow. Once you create one, the chip is replaced by the Pull Request number and its status.
Not the promotion button of the branch window
The branch window can also show a Create promotion from integration (experimental) button,
but only on a project that turns on enablePromotionBranches, which this one does not. That
feature is for promoting a subset of what is waiting. What you are doing is promoting
everything, and everything is what a plain Pull Request from one branch to the next carries.
Title it for the humans who will read it, not for git, and call it a promotion:
Promotion 2026-09: crew capacity cap, quote PDF
Release is the word for the Pull Request into main, production, and for nothing else. The
business reads "release" as "it is live", and a Pull Request into uat puts nothing live. Keep the
two words apart and a list of Pull Requests reads like the history of what reached production.
Merge it with Merge pull request, never with a squash: a promotion carries every commit of the stories it promotes, and the next promotion, the retrofit and the release notes all need to find them one by one (Lab 1.6).
4. Read the deployment actions it carries¶
Once the check runs, the sfdx-hardis comment gains two sections, Pre-deployment Actions Results and Post-deployment Actions Results. What a promotion adds on top is the paragraph naming the scope (1): every Pull Request it carries, each one a link.
Every action any contributor declared on any of the merged stories is collected into one table, with its label, its type, its status and a link back to the Pull Request it came from. Anything needing a human gets a checklist above the table, headed Manual Actions to perform before proceeding with deployment or after deployment. The two checklists land on different jobs: the check job carries the before one (2), so you can act on it while deciding, and the merge job carries the after one. The post-deployment actions (3) read skipped on the check: a check changes nothing in the org, so they wait for the merge.
Read it before merging. Two things to look for:
| What you see | What it means for you |
|---|---|
| A manual step | Somebody has to click something in UAT. That somebody is you, and it will not happen unless you plan it |
| A data import | Records will be written to UAT. Testers may have their own records there |
You cannot edit a contributor's action from here: it belongs to their Pull Request and to every org after this one, so a wrong action is fixed in a new Pull Request rather than in this promotion.
The checklist is the exception, and it is not decoration. Tick a box once you have done the thing in the org, and the next sfdx-hardis job reads the box back and records the action as done. Leave it unticked and the next promotion will still be asking you for it.
5. Merge and watch the deployment¶
Merge the promotion. The Process Deployment (sfdx-hardis) run starts, this time on uat.
This is the first deployment to this org through the pipeline, so it will be larger than the ones to integration: UAT is behind by everything the team has done. Expect several minutes.
When it finishes, do the manual steps the comment listed, in helios-uat.
Then read the log for the overwrite manager, above the deployment, among the lines that start
with [NoOverwrite]:
Type RemoteSiteSetting: 1 item(s) skipped because they already exist in the target org (protected), 0 item(s) to deploy
helios-uat already has Helios_Warehouse, so the promotion left it out of the package, and the
Final package.xml to deploy printed right after it has one item fewer.
6. Verify with a tester's eyes¶
Open helios-uat and check the two stories are genuinely usable, not just deployed:
- A crew larger than the cap is brought back down to the cap when you save: put
Crew Capacity Capat 3 andCrew Sizeat 6 on a planned installation, save, and it reads 3 - The quote PDF permission is on the manager permission set
- Setup > Remote Site Settings still says
https://warehouse-test.helios.invalidforHelios_Warehouse: the promotion left it alone
Deployed and usable are different states, and the gap between them is almost always a permission or a piece of reference data.
7. Generate the release notes¶
Open the DevOps Pipeline panel and click the uat node, the same way you clicked integration
in step 1. In the footer of that window, the left button now reads Generate Promotion Notes for
uat. Click it.
It asks one question, Select the merge commit for this release or promotion, listing the merges
that landed on uat, newest first. Take the top one, Merge pull request #N from
your-handle/integration: the promotion you have just merged. The notes cover what that merge
brought into uat, and nothing before it.
The button is named after what the branch is. uat merges into preprod, so what arrived there is
a promotion. On a branch with no merge target, main, the same button reads Generate Release
Notes for Latest Release in main, and once you pick a go-live in the selector at the top of the
window it reads Generate Release Notes for that go-live.
Next to it, Preview Upcoming Promotion Notes from uat does the same thing for what has not been promoted yet. It is the one to use on a Wednesday, when somebody asks what Thursday's release will contain.
You get a markdown document listing the Pull Requests, their authors, their stories and the manual
steps, generated from the merge history rather than from anybody's memory. It lands under
hardis-report/release-notes/, in a folder named after the release tag and the date, or after the
target branch and the date when there is no tag, so here uat-<date>. Markdown and PDF every time,
plus a spreadsheet when there is anything to put in it.
On this promotion, the generated notes open like this:
# Promotion Notes - uat
| Metric | Value |
|------------------|-------|
| Pull Requests | 19 |
| Tickets | 15 |
| Contributors | 1 |
| Added / Modified | 33 |
Then come a table of the tickets, one of the Pull Requests with their authors and merge dates, the
metadata changed by type, and the deployment actions with their status in uat: the manual
deliverability step still manual, the imports and the schedule success.
Read it and then improve it. Generated notes are a complete list, and a release note the business reads needs two things the generator cannot know:
- One sentence at the top saying what this release is for. "Crews can no longer be over-staffed, and sales can generate quote PDFs."
- The manual steps, stated as instructions to a named person, not as a technical list
Then give them to the people who read them. Open the promotion Pull Request you merged, ... at the top right of its description, Edit, and paste the improved notes in place of the one-line description. A merged Pull Request stays editable, and it is where the release is: its link is what you send the business, and what the release notes of the next promotion point back to.
The sentence (1) says what the promotion is for, in the business's words. The manual step (2) names who does it, where, and what breaks if nobody does. The rest is a short table of what to test, and a pointer to the full generated list for whoever wants it.
Under the hood: what generated the notes, and what a promotion really is
The command was:
sf hardis:doc:release-notes --mode post --target-branch uat
which walks the git history between two references, collects the merge commits, matches each one with its Pull Request through the git provider API, and pulls the title, author, body and declared deployment actions.
That API call is the part that can quietly fail. The command needs a git provider token, taken
from the environment (GITHUB_TOKEN or CI_SFDX_HARDIS_GITHUB_TOKEN on GitHub). With no token it
does not stop: it warns, collects zero Pull Requests, and writes you a perfectly formatted document
with nothing in it. An empty release note is more often a missing token than an empty release.
This is also why a promotion is never squashed. The notes find each story from the commit its
Pull Request left on integration, a merge commit or the single commit of a squash alike: that is
why a feature Pull Request can be squashed. A promotion squashed into uat would replace all of
those commits by one that no story produced, and the notes of uat would name the promotion and
nothing it carried. The DORA report in Lab 3.6 leans on the same link.
A promotion is an ordinary Pull Request. There is no special promotion machinery in the default
setup: integration into uat is a branch merged into another branch, and the deployment job on
uat behaves like the one on integration. What differs is only what the configuration says about
uat: its org, its merge targets, and whether delta deployment applies between major branches
(enableDeltaDeploymentBetweenMajorBranches, off by default, because a promotion is the worst
moment to discover the target org drifted).
There is an experimental feature for teams who want to promote a subset of what is waiting, rather than everything: promotion branches. It is worth reading about once you have done a few releases the ordinary way.
What you should see¶
- The
uatbranch carrying everythingintegrationhad - A green Process Deployment (sfdx-hardis) run on
uat - Both stories working in
helios-uat Helios_Warehouseinhelios-uatstill pointing at the test warehouse- The release notes in the description of the promotion Pull Request
If it goes wrong
The check fails with authentication errors for uat.
Lab 3.1 for the uat branch: the secrets, and the pre-authorisation of the External Client App in
helios-uat.
The deployment fails on something that worked in integration. The orgs differ. Usually UAT is missing a feature, a licence, or a component somebody deleted there by hand. Read the error and check the org.
The promotion Pull Request shows hundreds of files. That is expected on a first promotion: UAT is behind by the whole history. It settles after this one.
The release notes are empty. Three causes, in order of likelihood: the wrong merge commit was picked, so check that the top of the list was the promotion; no git provider token in the environment, so the Pull Request lookup returned nothing and only warned; or the merges were squashed, so there is no link to look up.
Check your work¶
Welcome page > Training: Level 3 > Check my work, then pick Lab 3.5.
Go deeper¶
Next: Lab 3.6 - Release to production and read your DORA metrics






