This is a common mistake - using a single static boolean to control recursion - it will generally work OK if the transaction size is <= 200 records but there are use cases when the transaction size will be greater than 200:
- Bulk API
- Platform Event triggers
- DML of some list of objects, size > 200 such as your execute anon
To control recursion it is best to remember the Ids in a static set and check the set membership before processing the sobject. This obviously works only after insert, before/after update.
Alternatively, make the code logic immune from doing double work, even if presented with the same object again (by checking sobject field values).
Now, when using static variables, there is a special case to consider which has to do with partial successes on an allOrNone=false
Database.update
or Database.insert
operation. SFDC will automatically retry the partial successes but won't clear the static variable on the retry. So, if your method is, say, trying to do callouts, the recursion control will prevent the callouts from happening on the partial success retry. And the callouts will never happen! (This can arise in API calls to your org where the DmlOptions are set to AllOrNone=false)
When a DML call is made with partial success allowed, more than one attempt can be made to save the successful records if the initial attempt results in errors for some records. For example, an error can occur for a record when a user-validation rule fails. Triggers are fired during the first attempt and are fired again during subsequent attempts. Because these trigger invocations are part of the same transaction, static class variables that are accessed by the trigger aren't reset. DML calls allow partial success when you set the allOrNone parameter of a Database DML method to false or when you call the SOAP API with default settings. For more details, see Bulk DML Exception Handling.
See this answer for a workaround or this lengthy blog post for more explication