Install
$ agentstack add skill-jhillock1-salesforce-claude-skills-salesforce-prod-deploy ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Production Deployment Safety
When to Use
- Before ANY production deployment
- When promoting validated sandbox work to production
- When John says "deploy to prod", "promote to prod", or "go live"
STOP — Before You Do Anything
Never deploy to production without completing ALL pre-flight checks. No exceptions. Run them in order. If any check fails, stop and resolve before continuing.
Production Deploy Gate — Approval + Manifest Scope
Every production deploy must satisfy two non-negotiable controls. These are enforced at the repo level (in casechek-salesforce via a hook + wrapper script) and are the recommended pattern for any org using this skill. Adapt the paths to your repo — don't assume casechek's layout maps onto a different org.
1. Approval-Gated — a human owner must say yes
A production deploy must stop at a permission prompt that only the org owner can approve. The deploy cannot proceed autonomously; an agent (or any non-owner) hitting the gate is blocked until the owner explicitly approves.
Recommended enforcement: a PreToolUse hook that intercepts deploy commands targeting production and requires interactive approval before the underlying sf project deploy start runs. In casechek-salesforce this is .claude/hooks/prod-deploy-gate.sh. Other orgs should wire an equivalent hook (or CI approval step) — the key property is that prod deploys are not self-serve.
2. Manifest-Scoped — name exactly what ships
A production deploy must declare an explicit, bounded set of components via a checked-in manifest (package.xml) or an explicit --source-dir. Untargeted deploys and --all-style "deploy everything" are denied — they are the mechanism by which drifted, unrelated metadata escapes into prod (see the baseline-tag-drift gotcha below).
Recommended pattern: keep a per-unit-of-work manifest and pass it explicitly. In casechek-salesforce, manifests live at manifest//package.xml (keyed to the Jira delivery ticket), and the canonical command is:
./scripts/deploy.sh production --manifest manifest//package.xml
The wrapper feeds the manifest to sf project deploy start --manifest …. A targeted --source-dir is the equivalent escape hatch for one-off cherry-picks. What's rejected is a deploy with no explicit scope — those are blocked by the gate, not merely discouraged.
> Other orgs: substitute your own manifest location and wrapper. The rule that travels is: prod deploys are approval-gated and explicitly scoped — never untargeted, never --all.
Sandbox Deploy Validation (Pre-Flight Lite)
Before deploying to sandbox, run these quick checks to avoid deploy-debug loops:
1. Metadata Dependencies Exist
# Check that all referenced custom metadata types, fields, objects exist in sandbox
# Common failure: deploying a flow that references Slack_Configuration__mdt before the type exists
sf data query --query "SELECT DeveloperName FROM CustomObject WHERE DeveloperName='Slack_Configuration'" --target-org sandbox --tooling-api --json 2>/dev/null || echo "MISSING"
Deploy order for sandbox too: Custom Metadata Type → Fields/Records → Flows → UI Components. The same wave ordering applies — don't skip it just because it's sandbox.
2. Check rollbackOnError Behavior
When deploying via sf project deploy start, Salesforce uses rollbackOnError: true by default. This means:
- If any component fails, ALL components in the deploy are rolled back — even the ones that succeeded
- The deploy output shows "18 successes" but they're all reverted if there's 1 failure
- You must re-deploy everything, not just the failed component
Mitigation: Deploy in small waves. If Wave 2 fails, Wave 1 is already committed and safe.
3. Post-Deploy Flow Activation Verification (EVERY TIME)
# ALWAYS run this after deploying ANY flow — sandbox or prod
sf data query --query "SELECT DurableId, ActiveVersionId, LatestVersionId, ApiName FROM FlowDefinition WHERE ApiName IN ('Flow_1','Flow_2')" --target-org sandbox --tooling-api --json
If ActiveVersionId != LatestVersionId: Your deploy created a draft. The OLD version is still running. Activate via Tooling API:
curl -X PATCH "https://casechek--partial.sandbox.my.salesforce.com/services/data/v64.0/tooling/sobjects/FlowDefinition/" \
-H "Authorization: Bearer $(sf org display --target-org sandbox --json | jq -r '.result.accessToken')" \
-H "Content-Type: application/json" \
-d '{"Metadata": {"activeVersionNumber": N}}'
This is the #1 cause of "my deploy didn't work" confusion. It happens in sandbox too, not just prod.
4. Deactivating Flows
To deactivate a flow, you must also use the Tooling API — deploying with Obsolete does NOT deactivate it:
# Set activeVersionNumber to 0 to deactivate
curl -X PATCH ".../tooling/sobjects/FlowDefinition/" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"Metadata": {"activeVersionNumber": 0}}'
Production Pre-Flight Checks
1. Drift Detection — Are we overwriting someone else's work?
20 admins touch this org. Retrieve the prod versions of every file you're about to deploy and diff against your branch.
# Retrieve prod state of modified files
sf project retrieve start \
--target-org production \
--output-dir /tmp/prod-current \
--metadata "Flow:FlowName1,Flow:FlowName2"
# Diff against your branch
diff /tmp/prod-current/force-app/ force-app/ --brief
Specifically check the high-risk modified files:
# Get list of files modified (not new) on this branch vs main
git diff main --diff-filter=M --name-only
For each modified file:
- Retrieve from production
- Diff against main branch (not your feature branch) — this shows admin changes since your last sync
- If drift found → merge admin changes into your branch first
If you skip this step and overwrite an admin's change, it's gone. No undo in Salesforce.
2. Dependency Map — What needs to exist before what?
Wave 0: Custom Metadata Type object definitions (if new to prod)
└─ Must exist before fields/records can deploy
└─ Include parent object-meta.xml alongside field definitions
Wave 1: Custom Fields + Standard Value Sets (picklists) + Custom Metadata Fields/Records
└─ Everything else depends on these existing
Wave 2: New Flows + Apex Classes + Test Classes
└─ Referenced by quick actions and pages
Wave 3: Quick Actions
└─ Referenced by flexipages
Wave 4: List Views + Path Assistants + Page Layouts
└─ Reference fields and actions
Wave 5: Flexipages (Case Record Page, Service Home)
└─ Reference everything above
Wave 6: Modified Existing Flows (HIGHEST RISK)
└─ Deploy last, rollback first if broken
3. Queue/ID Verification — Are hardcoded IDs correct for prod?
Flows often contain hardcoded queue IDs, user IDs, or record type IDs from sandbox. These are DIFFERENT in production.
# Find hardcoded IDs in your changed files
git diff main --diff-filter=AM -- '*.flow-meta.xml' '*.cls' | grep -E '[0-9a-zA-Z]{15,18}' | grep -v "apiVersion\|xmlns"
# Cross-reference: Get prod queue IDs
sf data query --query "SELECT Id, DeveloperName, Name FROM Group WHERE Type='Queue'" --target-org production --json
# Get prod record type IDs
sf data query --query "SELECT Id, DeveloperName, SObjectType FROM RecordType WHERE SObjectType='Case'" --target-org production --json
If ANY ID doesn't match prod, the flow will error at runtime. Fix in code before deploying.
4. Automation Audit — What else references what you're changing?
Before modifying status values or fields, check what references them:
# Find all flows referencing a status value
grep -rl "Waiting on Customer\|Waiting on Integrations\|Waiting on Casechek" force-app/main/default/flows/
# Find validation rules that might reference old values
grep -rl "Waiting on" force-app/main/default/objects/Case/validationRules/
# Find reports (if retrieved)
grep -rl "Waiting on" force-app/main/default/reports/ 2>/dev/null
# Check process builders (legacy)
grep -rl "Waiting on" force-app/main/default/workflows/ 2>/dev/null
Also query prod directly:
# Flows referencing a field
sf data query --query "SELECT Id, Definition.DeveloperName FROM FlowVersionView WHERE Status='Active' AND Definition.DeveloperName != null" --target-org production --json
5. Field Name Drift — Do sandbox names match production?
Sandbox and production can have different API names for the same conceptual field. This will cause flexipage/layout deploys to fail silently or error at runtime.
# For each custom field referenced in flexipages/layouts, verify it exists in prod
sf data query --query "SELECT QualifiedApiName, DataType FROM FieldDefinition WHERE EntityDefinition.QualifiedApiName='Incident' AND QualifiedApiName LIKE '%Jira%'" --target-org production --json
Known drift examples:
Jira_Link__c(sandbox) →Jira_Ticket_Link__c(production) on Incident object
If a field name doesn't match prod, update your local source before deploying. A flexipage referencing a nonexistent field will fail with: "Something went wrong. We couldn't retrieve or load the information on the field: Record.FieldName__c"
6. Dry Run — Does it even deploy?
# Validate each wave WITHOUT deploying
sf project deploy start --source-dir --target-org production --dry-run
If dry-run fails, fix before proceeding. Common causes:
- Missing dependency (deploy order wrong)
- Test class failure (check Apex tests pass)
- Profile/permission conflicts
7. Apex Tests — Do they pass in prod context?
# Run your test classes against production
sf apex run test --class-names CaseLifecycleFlowTest --target-org production --wait 10
Wave Deployment Execution
Create a Backup First
# Tag current prod state
BACKUP_DIR="prod-backup-$(date +%Y%m%d-%H%M)"
mkdir -p "$BACKUP_DIR"
# Retrieve everything you're about to overwrite
sf project retrieve start \
--target-org production \
--output-dir "$BACKUP_DIR" \
--metadata "Flow:Flow1,Flow:Flow2,FlexiPage:Page1"
echo "Backup saved to $BACKUP_DIR"
Deploy Each Wave
# Wave 1: Fields + picklists
sf project deploy start --source-dir force-app/main/default/objects/Case/fields/ \
--source-dir force-app/main/default/standardValueSets/ \
--target-org production
# Wave 2: New flows + Apex
sf project deploy start --source-dir force-app/main/default/flows/New_Flow_1.flow-meta.xml \
--source-dir force-app/main/default/classes/ \
--target-org production
# ... continue per wave
Verify after each wave before proceeding to the next:
# Quick sanity check — does the component exist?
sf org list metadata --metadata-type Flow --target-org production | grep "FlowName"
Flow Activation Verification (CRITICAL)
Deploying a flow with Active to an org that already has an active version creates a NEW DRAFT version, NOT an active one. The old version stays active.
After deploying any flow, ALWAYS verify:
# Check if active version matches latest version
sf data query --query "SELECT DurableId, ActiveVersionId, LatestVersionId, ApiName FROM FlowDefinition WHERE ApiName='Your_Flow_Name'" --target-org production --tooling-api --json
If ActiveVersionId != LatestVersionId, the deploy created a draft. Activate manually:
# Get the latest version number
sf data query --query "SELECT VersionNumber, Status FROM FlowVersionView WHERE FlowDefinitionViewId='' ORDER BY VersionNumber DESC LIMIT 5" --target-org production --json
# Activate via Tooling API
curl -X PATCH "https://casechek.my.salesforce.com/services/data/v64.0/tooling/sobjects/FlowDefinition/" \
-H "Authorization: Bearer $(sf org display --target-org production --json | jq -r '.result.accessToken')" \
-H "Content-Type: application/json" \
-d '{"Metadata": {"activeVersionNumber": N}}'
If you skip this step, the old flow version keeps running. Users will see stale behavior and you'll wonder why your deploy "didn't work."
Wave 6: Modified Flows (Do Last)
This is the danger zone. These are live flows that agents use right now.
- Deploy the modified flow
- Immediately test the affected action in the org (e.g., click Escalate Case)
- If it errors → rollback from backup:
sf project deploy start --source-dir "$BACKUP_DIR/force-app/main/default/flows/Broken_Flow.flow-meta.xml" --target-org production
Rollback Plan
If something breaks after deploy:
# Rollback specific component from backup
sf project deploy start \
--source-dir prod-backup-YYYYMMDD-HHMM/force-app/main/default/flows/BrokenFlow.flow-meta.xml \
--target-org production
# Rollback entire wave
sf project deploy start \
--source-dir prod-backup-YYYYMMDD-HHMM/force-app/ \
--target-org production
New components (fields, new flows) don't need rollback — they're additive and harmless. Deactivate new flows in the UI if needed.
Modified components are the rollback priority — restore from backup immediately.
Migration Flows (Special Handling)
One-time migration flows (e.g., status value conversion):
- Deploy as Inactive
- Run manually in prod via Setup → Flows → Run
- Spot-check results: query 10 affected records
- Deactivate/delete after confirmed
# Verify migration results
sf data query --query "SELECT Status, Waiting_On__c, COUNT(Id) FROM Case WHERE IsClosed=false GROUP BY Status, Waiting_On__c" --target-org production
Timing
- Deploy during low traffic: Before 7 AM ET or after 6 PM ET
- Never deploy Friday afternoon — you won't catch errors until Monday
- Best day: Tuesday or Wednesday morning — full week to catch issues
- Migration flows: Run before agents log in
Post-Deploy Checklist
- [ ] Each wave deployed successfully
- [ ] Modified flows tested manually in prod
- [ ] New list views visible to correct profiles
- [ ] Page layout changes showing on Case record
- [ ] Quick actions appearing on Case page
- [ ] Migration flow run and verified (if applicable)
- [ ] Old status values NOT removed yet (keep inactive for 2 weeks)
- [ ] Agents notified of changes
- [ ] Backup directory retained for 30 days
Don't Forget
- Status values: Never remove old values in the same deploy. Keep them for 2+ weeks until confirmed nothing references them.
- Flow versions: Deploying a flow creates a new version. Old version is still there. If you need to rollback, activate the previous version in Setup → Flows.
- Field-level security: New fields may not be visible to all profiles. Check FLS after deploying fields.
- List view visibility: New list views default to "visible to me only." Set sharing to appropriate groups.
Gotchas (Hard-Won)
./scripts/deploy.sh production Baseline Tag Drift (HIGH-RISK)
The wrapper computes its diff base from the last deploy-production-* tag. If that tag is stale, your deploy can sweep in ~200 unrelated drifted files alongside your actual change. This is how a worktree branch deploy escaped scope and overwrote unrelated UI in prod (Guide tab + publisher actions, 2026-04-22).
Before running ./scripts/deploy.sh production:
# Inspect what the script will deploy — compare deploy-production-* tag to HEAD
git tag -l 'deploy-production-*' | sort -r | head -3
git diff --stat $(git tag -l 'deploy-production-*' | sort -r | head -1)..HEAD | tail -1
If the diff shows files outside your change scope, DO NOT use the wrapper. Use a targeted sf project deploy start --source-dir instead, listing only the files you intended to ship.
Targeted cherry-pick deploy:
sf project deploy start \
--source-dir force-app/ma
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [jhillock1](https://github.com/jhillock1)
- **Source:** [jhillock1/salesforce-claude-skills](https://github.com/jhillock1/salesforce-claude-skills)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.