Platform App Builder study materials
Platform App Builder study resources

Bulk Case Closure That Doubles Up Tasks

Platform App Builder ยท Put your knowledge to work in context.

All case studies

Bulk Case Closure That Doubles Up Tasks

Halcyon Field Service closes cases in bulk. An after-save record-triggered flow on Case stamps Asset.Last_Service_Date__c and creates a follow-up Task. A before-save flow sets Priority when a custom Severity__c field changes. A legacy workflow rule with a field update on Case is still active, along with two Process Builder processes that update Case.Status__c. Last month a Data Loader job closed 8,000 cases in the nightly window. It failed with 'Apex CPU time limit exceeded' after 40 minutes; when it was rerun the next night, agents found about 300 cases with two identical follow-up tasks and roughly 60 assets whose Last_Service_Date__c was stale despite a task being created.

1. Explain the duplicate tasks and the stale asset dates, naming the mechanics rather than the symptoms.

Read the worked answer

Duplicate tasks: recursion through field-update re-saves. When a workflow rule (or Process Builder) performs a field update, the record is saved again. That second save re-fires before-save logic and the after-save flow, so the flow that creates a Task executes twice for the same case. Nothing in the flow is idempotent - there is no guard field and no entry criteria that would block the second pass. The reopen-then-close scenario is the same disease: the flow has no memory of having run.

Stale asset dates: partial success across batches. Each Data Loader batch of 200 is its own transaction. The 8,000-record run hit the CPU limit mid-flight; batches committed before the failure kept their tasks, and a batch that faulted without a fault path rolled back its asset stamp while an earlier pass had already created a task. Rerunning the whole file then produced a second task for every case that already had one, and left the asset untouched wherever the flow faulted.

Why it is slow in the first place: the after-save flow crosses objects (Case โ†’ Asset, Case โ†’ Task) one record at a time, and it is stacked on top of a workflow field update plus two Process Builders that each cause their own save cycle. Four automations firing per record, thousands of times, is how you reach a CPU limit.

2. Rewrite the automation so it is bulk-safe and idempotent. Be specific about which flow type does what.

Read the worked answer

Consolidate to two flows and delete the rest.

Before-save flow (same record only, no DML):

  • Set Priority from Severity__c.
  • Set the guard field when the case reaches Closed: Follow_Up_Created__c = true only after the task exists - which means it cannot live here, so instead use a 'closing trigger' field set here (e.g., Closure_Stamp__c), and let the after-save flow use it.

After-save flow (does the DML):

  • Entry criteria: IsClosed = true AND Follow_Up_Created__c = false. This is what makes it idempotent - the workflow field update re-save and any manual re-save hit the criteria and stop.
  • Create Task.
  • Update the case: Follow_Up_Created__c = true. That update re-enters the flow, the entry criteria fail, and the loop terminates after one extra evaluation.
  • For assets: Get Records on the related Assets into a collection, then Update Records using that collection, so the flow performs one DML for many records rather than one per case. Where the asset update is heavy, move it to a scheduled path so it runs outside the closing transaction.

Retire the workflow rule and both Process Builders - migrate their field updates into the before-save flow, then deactivate and delete. Before-save flows on the same object are the correct home for field updates and cost far less than the old engines.

Still to check: whether any remaining automation writes back to Case after save; every such write is a fresh recursion risk.

3. What is your proof that the fix works before it reaches production?

Read the worked answer

Prove it with volume, not with a single test record.

  • Sandbox with production-like data. Load 8,000 cases in batches of 200, in the same window, with the same integration user.
  • Assert the invariants, not the vibe: count(Task where CaseId in closed set) == count(closed cases), and every closed case has a non-null Asset.Last_Service_Date__c where an asset exists. Write these as SOQL counts before and after.
  • Read the limits block in a debug log from the largest batch. CPU time should be an order of magnitude below the limit; if it is not, the flow is still doing per-record DML.
  • Exercise the re-entry paths explicitly: reopen then re-close a case, run a second Data Loader pass over the same file (it should create zero new tasks), and close a case from the UI and from the API.
  • Test the fault path - point one row at a missing asset and confirm the batch survives and the error is visible, rather than a silent rollback.
  • Only then deploy, and keep the old flow inactive-but-present for one release so reverting is a status change rather than a rebuild.