How to add a deployment workaround
When a metadata type cannot be deployed as-is — it references a user that does not exist in the target org, or hits a platform limit — you add a workaround that patches the metadata before the push and reverts it after. This guide writes one; for the rule that separates a workaround from a custom deployment step, see workarounds vs. custom steps. See the workaround reference for the existing set. For keeping a deploy from touching (or overwriting) something in the first place, see the forceignore contexts how-to instead.
The contract
Every workaround follows the module contract; the module below satisfies it.
Write the module
// scripts/dia-scripts/deploy/handle-my-thing.mjs
import { parseArgs } from "node:util";
import { existsSync } from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import { ROOT_DIR, enableDebugFromArgs } from "../utils/env.mjs";
import { printHeader } from "../utils/log.mjs";
import { readXml, writeXml, replaceMatchingValues } from "./xml-file.mjs";
import { isStandaloneRun } from "./workaround-module.mjs";
export function parseArgumentsToOptions(argv) {
const { positionals, values } = parseArgs({
args: argv,
options: {
mode: { type: "string", short: "m" },
"target-org": { type: "string", short: "o" },
debug: { type: "boolean" },
},
strict: false,
allowPositionals: true,
});
enableDebugFromArgs(values);
return {
mode: values.mode ?? positionals[0] ?? "",
targetOrg: values["target-org"] ?? "",
};
}
export async function run({ mode, targetOrg }) {
if (mode !== "apply" && mode !== "revert") {
throw new Error('mode must be "apply" or "revert"');
}
// find your files, back them up on apply, restore on revert…
}
// Lets `node handle-my-thing.mjs --mode apply` run the workaround directly; the chain imports `run`.
if (isStandaloneRun(import.meta)) {
await run(parseArgumentsToOptions(process.argv.slice(2)));
}
Copy the closest existing workaround
The fastest correct start is to copy a workaround that already does something similar —
handle-auth-provider.mjs for a user-reference fix, remove-queues-from-metadata.mjs for a
strip-before-deploy — and adapt it. They already implement the backup/revert dance correctly.
Register it in the workaround chain
Register the workaround in scripts/dia-scripts/deploy/workaround-chain.mjs — once, and nowhere else
(which consumer runs which tag: workaround reference):
// scripts/dia-scripts/deploy/workaround-chain.mjs
import * as handleMyThing from "./handle-my-thing.mjs";
const WORKAROUND_CHAIN = [
// …
{
workaround: handleMyThing,
runsOn: [SCRATCH], // where it runs — see below
revert: REVERT_BY_MODE, // it accepts `--mode revert`
needsTargetOrg: true, // pass `--target-org` on apply
},
];
Pick the runsOn tags from what the workaround fixes — tag semantics in the
workaround reference, the decision rules
(tags, chain vs. explicit caller) in
workarounds vs. custom steps. Use
REVERT_BY_STRIPPING_BLOCK only for an apply-only workaround that exports FORCEIGNORE_BLOCK_OWNER.
Verify
node scripts/dia-scripts/deploy/handle-my-thing.mjs --mode apply --target-org <org>then--mode revert, checking the working tree comes back clean.- Run
npm run lintandnpm run format. - Deploy to a scratch org and confirm the component that failed without it deploys; for an
ORG_DEPLOYworkaround, runnpm run deployagainst a sandbox instead. - Confirm the workaround's header appears in both the pooled delta-validation log and the full-push log.
Reverts must be symmetric
If apply leaves a .bac behind, revert must restore it — a local run must leave the working
tree clean.