Defining a Copado Story for Script Deployment
Every so often a release manager asks me a question that sounds trivial and is not. How do I raise a user story when there is nothing to deploy? No Apex, no flows, no permission sets, no metadata at all. Just a script that has to run against the target org at the right point in the promotion.
It comes up more often than people expect. A data backfill that has to land before a feature is switched on. A quantity correction applied across a set of records. A scheduled job stopped before a deployment and started again afterwards. Setting the last refresh date on an environment record after a sandbox refresh. All of it is release work, all of it needs to be auditable, and none of it is metadata.
The temptation is to do the work by hand, outside the pipeline, and let the story carry a note saying "run the script". That is the thing to avoid, because the moment the work leaves the pipeline the evidence leaves with it.
Start with the story, not the script
Raise the user story the way you raise any other. Give it a real title that says what will change in the org rather than what will be typed, attach it to the release and the project, and let it move through the same statuses as everything else. An empty story is still a story: it has an owner, an approver, a release, and in due course a promotion and a deployment record.
What it will not have is any user story metadata, and Copado is relaxed about that. The consequence catches people out: when the story is promoted, the git side produces nothing worth deploying, so the promotion has to be given something else to do.
Commit the script, do not paste it
Put the script in the repository on the same feature branch the story created, under a path that is clearly not metadata. Something like scripts/release/US-0001/ does the job. The commit page in Copado is built around selecting metadata, so the usual route here is a plain git commit onto the story branch from your own working copy.
That is not a detail. A script on the story branch is reviewed in the same pull request, moves through the same merges, and lands in the target branch at the same moment. A script pasted into a description does none of those things.
Give the promotion something to run
There are three practical ways to make the script execute.
A Copado Function attached to the pipeline is the one I reach for. The container already has the branch checked out, so the script is simply there, and the Context supplies the session and endpoint for the target environment without anyone pasting a session id anywhere. The result record is then your evidence that it ran and against what. If you have not built one before, start with Copado Functions and then making them run.
A manual task step on the deployment is the honest fallback. Somebody still has to do the work, but the step is visible, it has to be completed before the deployment closes, and the record shows who did it and when. An Apex step is right when the work is genuinely Apex, and wrong when what you actually want is a command line tool.
Write it so it can run twice
A script that only survives one execution is a liability in a pipeline, because pipelines retry. Check the current state before you change it, make the change conditional, and let a second run be a quiet no-op. Then make the script report, because a script that runs silently is indistinguishable from one that did not run at all.
On setting a value such as a quantity, do it through the CLI against the target rather than through the user interface, so it is repeatable and reviewable:
sf data update record \
--sobject OpportunityLineItem \
--record-id 00kXXXXXXXXXXXX \
--values "Quantity=25" \
--target-org "$TargetOrgAlias"
Refreshing the commit on an existing story
The related question is what to do when the script turns out to be wrong. While the story is unpromoted, keep it: commit the corrected script to the same branch and let the story carry the newer commit, because the unit of work has not changed. Once the story has been promoted and deployed, stop, and raise a new story for the correction. The history then reads as two deliberate changes rather than one story that quietly became something else.