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)andrun(options); - accepts
--mode apply, and--mode revertunless it is apply-only; - accepts
--target-orgwhen the chain entry setsneedsTargetOrg: true; - restores what it edited on
revert— from a.bacsibling written onapply, or viagit 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/, callsignoreRemovedPaths(FORCEIGNORE_BLOCK_OWNER, removedPaths)onapplyandrevertIgnoredRemovedPaths(FORCEIGNORE_BLOCK_OWNER)onrevert(both fromdeploy/prepare-forceignore.mjs); an apply-only workaround exportsFORCEIGNORE_BLOCK_OWNERand the chain strips its block on revert; - may self-run as
node deploy/<name>.mjs --mode applybehindisStandaloneRun(import.meta)(deploy/workaround-module.mjs) — the only place a module callsprocess.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.