Engineering Notes

Three pitfalls in a dashboard cache lifetime — boot-time restore, TTL, and partial invalidation

We added a cache-first design to the cross-site updates dashboard, then wired the site-list badge to read from the same cache. Both ships went well. But as soon as it hit real usage, we hit three distinct pitfalls around the cache’s “lifetime” in quick succession: “the badges all disappear on refresh,” “the 7-day TTL is too short,” “running maintenance on one site clears all the badges.” Each is a small spec call in isolation, but from the user’s side, all three feel like the same symptom: “badges aren’t sticking around the way I expect.” This post walks through each fix and what we got wrong. Pitfall 1 — All badges …

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
Engineering Notes

Detecting the running site from streaming logs — why log-order inference broke and how one marker fixed it

In a screen that runs maintenance across multiple sites in sequence, you want to show progress live: a blue border on “the site being processed now,” a green border on “completed sites.” Common UI. The approach we reached for first was “watch the streaming logs the backend emits, and infer which site is being processed.” It turned out to be surprisingly tricky — three rounds of fixes before it stabilized. This post records that trial-and-error as a design log. The first design — “site switched, so mark the previous one done” Logs arrive per-site as lines like [Site name] Starting maintenance. The first implementation was simple: // Initial implementation (later …

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
Engineering Notes

Implementing a dynamic OGP image generator for our blog — PHP GD, per-post 1200×630 cards

One day on our English X account, I posted a link to a fresh blog article and froze when the OGP card rendered: the image was the LP sales banner — “Stop babysitting updates. Start scaling maintenance revenue.” — not anything related to the post itself. Going back through the last seven announcement posts, every single one was showing the same LP sales banner. Articles written as technical engineering deep-dives had been quietly flowing through the X timeline for months looking like sales-promo posts. This article walks through how we found the structural cause and replaced it with a dynamic OGP image generation engine — using our own blog as …

Read more
Engineering Notes

Diff from the live server, not from your git history — when a local repo has drifted from production

An investigation agent flagged “the license API PHP returns Japanese-hardcoded messages” and we sat down to fix it. But something felt off the moment we opened the file — the version running on the production server didn’t match the latest commit in the local repo. Stranger still, production had more recent features than our local checkout. A bit of digging turned up the truth: months earlier, someone had hot-patched the production file in response to a different user issue, and that change had never been committed back to git. This post walks through how we detected that drift, and the two-stage strategy we used to merge production back into the …

Read more
Engineering Notes

Multilingual emails from a Stripe webhook — inferring language from currency

A subtle trap of taking a SaaS international: the system emails sent from your Stripe webhook. Purchase confirmation, renewal success, payment failure, plan change — four kinds of emails triggered by Stripe events. We recently discovered all four had been hardcoded to Japanese for months, sending Japanese receipts and failure notices to English-paying users overseas. The kind of bug that quietly persists forever unless you go looking. This post walks through the currency-based language inference design we landed on, plus the small mb_language trap that nearly ruined the fix. Where do you get “the user’s language” from? — three options There were essentially three design options for picking the email …

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