WordPress Maintenance

When the same selector appears twice on a WordPress admin page — Playwright strict mode and the .first habit

For sites where SSH isn’t available, we drive maintenance through Playwright by automating the WordPress admin UI. Plugin updates, theme updates, translation updates — all running fine. Until one day, only the Core (WordPress itself) update started failing. The error was Locator.click: strict mode violation — familiar to anyone who’s used Playwright, the one that fires when a locator matches multiple elements. Tracing it back, the root cause turned out to be a structural quirk of the WordPress admin UI sitting right at the trap line. This post walks through the bug from the angle of Playwright’s strict mode and making .first a habit. Strict mode violation is “your selector …

Read more
WordPress Maintenance

A pending-plugin-count badge on the 🔌 button — reusing the dashboard cache instead of doubling state

A client asked: “After I run a cross-site update check, can each site show — right in the site list — how many plugin updates are still pending?” Visually the answer was obvious: a small red badge on the top-right of the 🔌 plugins button, like an unread-notification count. Easy to specify. The harder question was where the data comes from. We could have added a fresh API endpoint and a new cache to hold “pending count per site.” But doing that would have doubled state management, and we already had a cache that knew this. We routed through the existing one. Here’s the reasoning behind that decision. Reuse the …

Read more
WordPress Maintenance

A days-since-last-maintenance badge — color-coding staleness across many sites

When you maintain a number of WordPress sites, showing the “last maintenance date” in the site list is the obvious move. A column of dates like 2026-05-21. But in actual use, that alone falls short. A client put it well: “Besides the last maintenance date, it’d help to also show how many days have passed. And it’d be even better if the color changed at 15 / 30 / 60 days so I can see the risk level.” This post walks through that step — from “absolute date” to “relative elapsed days + color” — including the small design details. Why a date alone isn’t enough An absolute date like …

Read more
WordPress Maintenance

Quieting PHP 8.2+ deprecated noise from older WP-CLI — three layers to keep JSON parse clean

Our multi-site maintenance tool fires wp plugin list –format=json against the sites it manages. One day, against a specific shared host (Xserver in Japan), this call started failing — and the failure mode was unusually subtle. Both the SSH connection test and the WP-CLI path test (wp –version) came back green. Users saw “all diagnostics pass, but the actual operation fails,” a frustrating asymmetry. Tracing it back, the root cause was PHP Deprecated warnings emitted by older WP-CLI (2.x) under PHP 8.2+ leaking into the JSON output. This post walks through the three-layer defense we used to structurally absorb the noise without losing real failures. What was happening — Deprecated …

Read more
WordPress Maintenance

Maintaining WordPress sites behind HTTP Basic auth — Playwright, urllib, and encrypted credentials

It’s pretty common to throw a layer of HTTP Basic auth on a WordPress site: a staging environment before launch, an internal test instance only employees should see, or any environment that wants an extra gate before the WordPress login screen itself. From a maintenance-tool point of view, this setup creates a peculiar “half-working, half-broken” asymmetry. The SSH/WP-CLI side runs fine. But everything HTTP-based — visual checks, thumbnail generation, browser-based fallback updates — hits 401 and dies. This post walks through how we resolved that asymmetry. What was breaking — two parallel paths, both blocked A maintenance tool actually touches a Basic-auth-protected site through two distinct paths: Playwright path: visual …

Read more
WordPress Maintenance

When paramikos defaults silently get your IP banned — the look_for_keys and allow_agent trap

One day a multi-site administrator reported a strange bug: “After running the app’s SSH connection test 2-3 times, my IP can’t reach SSH on that server for a long while.” The errors came back as Connection refused or Connection closed by …. The server wasn’t down, and SSH from a different IP worked fine. The source IP was being temporarily banned at the server. Two external investigation reports gave the cause: server-side protection mechanisms (fail2ban or PerSourcePenalties in OpenSSH 25+) detect short-windowed authentication failure spikes and temporarily ban the source IP. But the user had only clicked the test button 2-3 times — why were failures “spiking”? The answer turned …

Read more
WordPress Maintenance

When SSH commands hit a csh login shell — wrapping every command in /bin/sh -c across the codebase

One day a user reported an oddly asymmetric bug. In the “add new site” modal, picking an SSH profile and clicking “auto-detect WordPress install path” always failed with “no path found.” But clicking the WP-CLI path test button on the same SSH connection worked fine. Same credentials, same host — one succeeded, the other failed. Tracing it down, the culprit was an old foe: csh / bash incompatibility on the server side. This post walks through the fix, sweeping the same bug across the rest of the codebase, and the static-analysis test we added to keep it from coming back. The smoking gun — find: 2: unknown primary or operator …

Read more
WordPress Maintenance

Connection architectures for WordPress maintenance tools — mapping four products on a two-axis grid

While writing the comparison pages for ManageWP, MainWP, WP Umbrella, and InfiniteWP on our LP, I tried to line up the four products under a single “connection method” column — and got stuck. The same ManageWP gets called a “Hosted SaaS tool” in one source, a “Worker plugin tool” in another, and a “self-hosted” tool in yet another. On closer look, these labels are mixing two distinct axes into one column. Once you separate them, the four products fit cleanly into a two-axis, six-cell grid. This post lays out that grid and walks through what each cell means for day-to-day operations. Axis 1 — What gets installed on the client …

Read more
WordPress Maintenance

Playwright versus WordPress’s ‘admin email confirmation’ screen — how automation can clear the 6-month gate

If you drive the WordPress admin via Playwright for long enough, one day a screen you’ve never seen before will appear after login, and everything downstream stops working. Is admin@example.com still the correct admin email address? [ Yes, the email is correct ] [ Change the address ] That’s WordPress’s admin email confirmation screen. Roughly every six months, after the admin user logs in, this confirmation screen gets injected — standard behavior since WP 4.9. A human just clicks once. An automation script can’t see it without explicit handling. Why automation gets stuck A straightforward Playwright login looks like: page.fill(‘#user_login’, user) page.fill(‘#user_pass’, pwd) page.click(‘input[type="submit"]’) page.wait_for_load_state(‘domcontentloaded’) # Assumes we’re on the …

Read more
WordPress Maintenance

When WP admin shows a plugin update but WP-CLI doesn’t — making automation see proprietary updaters

You open the “Updates” page in WordPress admin and see that Elementor Pro / ACF Pro / vk-blocks-pro have updates available. Then you run wp plugin update –all from your automation, and those exact plugins don’t update. The asymmetry — “the admin sees it; WP-CLI doesn’t” — traces back to how WordPress detects updates and how premium plugins layer proprietary update mechanisms on top of that. Here’s the mechanism and how an automation tool can adapt. WordPress update detection is held by a transient cache WordPress doesn’t decide “is there an update?” on every request. It caches the answer in the wp_options table as a transient, valid for up to …

Read more