Your after-update updates the record, which fires your after-update. Breaking the loop without breaking legitimate re-entry.
Why a plain flag is wrong
A static 'hasRun' Boolean blocks the second batch of a 400-record update too — that is legitimate re-entry, not recursion. Track processed record Ids in a static Set instead.
Better: stop causing it
Most recursion is an after trigger doing a before trigger's job. Assigning field values on the in-flight record in before-save costs no DML and cannot recurse.
Practice in your org
- Open your Developer Edition org and follow along step by step
- Change one thing at a time and note what happens
- When something breaks, read the error before searching for it