A variable I'd refactored into one function — and kept referencing from another. Python's lazy evaluation hid it, and an AST test finally caught it
One day the browser automation flow started failing right after plugin updates with NameError: name 'plugin_form_selectors' is not defined in the post-update "residual check" step. The refactor that introduced this had landed back in v1.6.1. The error didn't surface until many rounds later. Reading the code, the cause is obvious in seconds — but nobody hit it for ages, because Python's lazy evaluation kept the leftover reference hidden until exactly the right execution path ran. This post walks through what the bug was and how we structurally prevented its kind via an AST static-analysis test. What happened — a reference that crossed a scope boundary browser_utils.py has two functions involved: run_browser_update_flow() , which orchestrates the whole update flow, and browser_update_remaining_plugins() , which handles only the plugin-update logic. The list of plugin-form selector candidates, plugin_form_selectors , used to be a local variable inside run_browser_update_flow() . In the v1.6.1 refactor — "let's split plugin update into its own function" — we created browser_update_remaining_plugins() and moved the plugin_form_selectors definition into it . # After v1.6.1 refactor def browser_update_remaining_plugins ( page , site , update_url ): plugin_form_selectors = [ # ← defined here ' #update-plugins-table-wrap form ' , ' form[name= " upgrade-plugins " ] ' , ' form[action*= " do-plugin-upgrade " ] ' , ' .plugins-php form ' , ] # ... update logic ... def run_browser_update_flow ( site , page ): # ... call to plugin updater ... browser_update_remaining_plugins ( page , site , update_url ) # ★ post-update "residual check" still uses the old local name for selector in plugin_form_selectors : # NameError if page . locator ( selector ). count () > 0 : pending_browser . append (...) The " after updating, make sure no plugin update forms are still visible " residual check stayed in run_browser_update_flow() . During the refactor, the call to extract this loop alongside the