The excluded-plugin setting that Playwright ignored — fixing browser-mode updates and false residual warnings
The symptom In browser-mode maintenance (Playwright, no SSH), plugins marked as "excluded from update checks" were still being updated. After the run, a "plugin updates remaining" WARNING email arrived every time. The excluded plugins were intentionally left behind, but the residual check treated them as unfinished updates and fired a warning — a two-part problem: wrong behavior and a misleading alert. SSH path vs. Playwright path On SSH-capable sites, WP-CLI's --skip-plugins flag carries the ignored_plugins list into the update command. That path already excluded them correctly. The Playwright path was different. browser_update_remaining_plugins() worked by clicking the "select all" checkbox on update-core.php and submitting the form — no filtering at all. # Before: select-all, ignored_plugins never consulted page . check ( ' input[name= " action " ][value= " update-selected-plugins " ] ' ) for cb in page . query_selector_all ( ' input[name= " checked[] " ] ' ): cb . check () This function is the chokepoint for two flows: pure browser-mode updates ( run_browser_update_flow ) and the browser residual pass that runs after SSH updates ( run_browser_residual_update , on by default). So even SSH sites could have excluded plugins updated by the residual pass. Browser-driven bulk updates are also outside the scope of pinpoint rollback, meaning there is no automatic recovery if a wrong update goes through. Fix 1 — _ignored_plugin_slugs() and _plugin_slug_from_checkbox_value() When excluded plugins are configured, the fix replaces the select-all approach with per-checkbox evaluation. def _ignored_plugin_slugs ( site : dict ) -> set [ str ]: raw = site . get ( " ignored_plugins " , "" ) return { s . strip (). lower () for s in raw . split ( " , " ) if s . strip ()} def _plugin_slug_from_checkbox_value ( value : str ) -> str : return value . split ( " / " )[ 0 ]. strip (). lower () if " / " in value else value . strip (). lower () Plugin checkboxes on update-core.php use a va