Skip to content

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 lint and npm run format.
  • Deploy to a scratch org and confirm the component that failed without it deploys; for an ORG_DEPLOY workaround, run npm run deploy against 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.