Skip to content

Deployment workarounds

A workaround is a task script under scripts/dia-scripts/deploy/ that patches metadata before a deploy or scratch-org push and reverts the patch afterwards. The rationale is in workarounds vs. custom steps; for keeping a deploy from touching or overwriting something, see the forceignore contexts how-to.

The module contract

A workaround module:

  • exports parseArgumentsToOptions(argv) and run(options);
  • accepts --mode apply, and --mode revert unless it is apply-only;
  • accepts --target-org when the chain entry sets needsTargetOrg: true;
  • restores what it edited on revert — from a .bac sibling written on apply, or via git checkout --;
  • edits XML through deploy/xml-file.mjs (readXml, writeXml, visitNodes, replaceMatchingValues, collectValues; xml2js parse/build). No workaround edits XML as text;
  • when it removes files from force-app/, calls ignoreRemovedPaths(FORCEIGNORE_BLOCK_OWNER, removedPaths) on apply and revertIgnoredRemovedPaths(FORCEIGNORE_BLOCK_OWNER) on revert (both from deploy/prepare-forceignore.mjs); an apply-only workaround exports FORCEIGNORE_BLOCK_OWNER and the chain strips its block on revert;
  • may self-run as node deploy/<name>.mjs --mode apply behind isStandaloneRun(import.meta) (deploy/workaround-module.mjs) — the only place a module calls process.exit().

Both consumers — the deploy orchestrator and org scratch-create — apply the list in deploy/workaround-chain.mjs, filtered by the tags each consumer represents, and revert it in reverse order. A chain entry has four fields:

Field Values
workaround The imported module.
runsOn One or more of ORG_DEPLOY, SCRATCH, SCRATCH_PUSH (below).
revert REVERT_BY_MODE — the module accepts --mode revert; REVERT_BY_STRIPPING_BLOCK — apply-only, the chain removes the module's .forceignore block.
needsTargetOrg Whether --target-org is passed on apply.

Task scripts under deploy/

Workaround Purpose
prepare-forceignore Swap the .forceignore context (deployment / scratchinit) and drop the ignores below the initial-push token for the run, so the DIA-scripts-driven push deploys them.
remove-entitlements-from-metadata Strip already-deployed Entitlements from the payload.
remove-installed-packages-from-metadata Drop managed-package refs when the version has not changed.
prevent-named-credentials-overwrite Remove already-deployed Named Credentials so secrets are not overwritten.
remove-queues-from-metadata Remove already-deployed Queues (avoids quick-deploy invalidation; enables chunking).
deploy-queues Deploy queues separately to work around the 16-queues-with-routing-config limit.
handle-chatbots Remove active bot versions before deploy, restore after (removed paths are hidden from the delta manifest; Agentforce agents excluded — see below).
handle-agentforce-agents Remove already-committed Agentforce agent versions before deploy, restore after (agent versions are immutable once committed; removed paths are hidden from the delta manifest).
handle-platform-event-subscriber-configs Replace the running user in Platform Event Subscriber Configs (System AutomatedProcess user).
handle-sso-settings Work around SSO settings/permissions (opt-in; commented out by default).
handle-auto-response-rules Replace Org-Wide Email Addresses in Auto-Response Rules.
handle-workflow-rules Replace user lookups and Org-Wide Email Addresses in Workflow Rules.
handle-approval-processes Replace user references in Approval Processes.
handle-custom-urls-in-sites Remove custom URLs in Sites.
handle-auth-provider Replace the execution email address in Auth Providers.
handle-custom-permission-connected-app Drop connectedApp references from Custom Permissions (the app does not exist in a scratch org).
remove-users-from-skills Strip user assignments from Skills.
disable-minimum-password-lifetime Disable the minimum-password-lifetime setting on scratch orgs.
assign-permission-sets Assign permission sets and wait for permission-set-group recalculation.
install-packages Install packages by 04t subscriber-package-version id from sfdx-project.json. Versions already present in the org are skipped.
deploy-packages Deploy packages from installedPackages/* metadata (--predeploy-packages). InstalledPackage is keyed by namespace, so two 2GPs sharing a namespace cannot be distinguished by this mechanism.
import-data / import-data-csvs Import seed data via sf data import tree (plan) / sf data upsert bulk (CSV); see how to seed data.

These files sit under deploy/ beside the workarounds but are not workarounds: workaround-module.mjs (isStandaloneRun), xml-file.mjs (the xml2js helpers), and the custom-steps-pre-deployment / custom-steps-post-deployment hooks. See workarounds vs. custom steps and add a custom pre/post deployment step.

Where each workaround runs

deploy/workaround-chain.mjs tags every workaround with the contexts it runs in. Apply order is list order; revert runs it backwards. The deploy orchestrator skips the revert on CI; org scratch-create always reverts.

Tag Selects
ORG_DEPLOY Workarounds for components a persistent org already has and must not receive again.
SCRATCH Workarounds for production-only users, addresses, URLs, or references the scratchinit context excludes.
SCRATCH_PUSH Workarounds for settings a fresh org rejects on its initial push.
graph LR
    subgraph chain["deploy/workaround-chain.mjs — one ordered list"]
        OD["ORG_DEPLOY workarounds"]
        SC["SCRATCH workarounds"]
        SP["SCRATCH_PUSH workarounds"]
    end
    DeployOrg["deploy orchestrator,
    persistent org"] --> OD
    DeployScratch["deploy orchestrator,
    scratch target"] --> OD
    DeployScratch --> SC
    Push["org scratch-create push"] --> SC
    Push --> SP
Consumer Tags applied
deploy orchestrator, persistent org target ORG_DEPLOY
deploy orchestrator, scratch org target ORG_DEPLOY, SCRATCH
org scratch-create push SCRATCH, SCRATCH_PUSH
Workaround Runs on --target-org Revert
remove-entitlements-from-metadata ORG_DEPLOY yes strips its .forceignore block
prevent-named-credentials-overwrite ORG_DEPLOY yes strips its .forceignore block
handle-chatbots ORG_DEPLOY yes --mode revert
handle-agentforce-agents ORG_DEPLOY yes --mode revert
handle-platform-event-subscriber-configs ORG_DEPLOY, SCRATCH_PUSH yes --mode revert
handle-sso-settings ORG_DEPLOY (opt-in, commented out) yes --mode revert
handle-auto-response-rules SCRATCH yes --mode revert
handle-workflow-rules SCRATCH yes --mode revert
handle-approval-processes SCRATCH yes --mode revert
handle-custom-urls-in-sites SCRATCH no --mode revert
remove-users-from-skills SCRATCH no --mode revert
handle-auth-provider SCRATCH yes --mode revert
handle-custom-permission-connected-app SCRATCH no --mode revert
disable-minimum-password-lifetime SCRATCH_PUSH no --mode revert

Outside the chain, explicit in the callers: prepare-forceignore (invoked at a consumer-specific point); remove-queues-from-metadata + deploy-queues in both consumers; remove-installed-packages-from-metadata in both, followed by install-packages in org scratch-create only. assign-permission-sets, deploy-packages and the data imports run in org scratch-create only. Which workarounds stay outside the chain, and why: workarounds vs. custom steps.

Removed paths and the delta manifest

A workaround that removes files writes an owner-tagged block into .forceignore:

# >>> removed by <owner>
force-app/main/default/queues/Some_Queue.queue-meta.xml
# <<< removed by <owner>

The owner is the workaround's file name (FORCEIGNORE_BLOCK_OWNER). The .forceignore context swap keeps existing blocks. The workaround's --mode revert strips its block; for an apply-only workaround the chain does. Why the block exists: delta deployments.

To add a new one, see Add a deployment workaround.