· 8 min read

The blog homepage before and after the rebuild, split by a diagonal with an arrow: the old cloud logo and menu on the left, the current logo and post grid on the right
The homepage on 31 July and on 4 August, at the same scale.

In late July I decided to rebuild this blog from scratch, and to do it with an AI. Not occasional help with a snippet: the whole redesign, the English translation, the SEO, the migration to production and the server maintenance.

It came to 72 commits in four days, 250 files and more than 18,000 lines. The result is measurable and it is better than what was there. And along the way four things broke, one of them in production, live.

This article tells both halves. The first gets told a lot; the second almost never, and it is the one that is actually useful.

What went well

Starting here, because without it the rest sounds like a complaint, and it is not. The site came out objectively better, and that is not an impression: these are figures anyone can check by visiting.

Tabla comparativa del blog antes y después de la reconstrucción: de 19 a 36 entradas, de ninguna copia de seguridad a copias diarias probadas
Same site, same server. Four days apart.

The last line deserves a caveat, because it is more interesting than it looks. I did have backups: the whole container is backed up with Proxmox Backup Server, which is what plenty of people do and is fine. What I did not have was a logical dump of the database, the kind you can pull a single table out of. It sounds like a minor distinction. Further down it turns out not to be.

The first thing to break: a pattern that was too greedy

Renaming 125 image files to descriptive names looked like the dullest task of the lot. The catch is that WordPress does not store an image, it stores a set: the original plus the sizes it generates from it.

To rename the whole set, the script asked for “every file starting with the old name”. With a screenshot of mine called login.png, that correctly finds login-1024x576.png and login-300x169.png. The problem is that the same folder held three other screenshots with no relation to the first: login1.png, login-npm.png and loginadmin.png. All of them start with “login”. It swept them up.

There was a second, nastier effect: when the new name began with the old one, the pattern picked up the file it had just created and renamed it again. That is how a ollama-listar-modelosar-modelos.png came to exist. The mistake was not picking the wrong name, it was using “starts with” without requiring a separator to follow.

Total damage: 24 original images with mangled names and 68 derivatives. And the script finished by reporting 97 files renamed successfully. No exception, no warning, no error. It surfaced only when checking afterwards, one by one, that all 141 image URLs still responded.

The second: deleting rows from a schema that was not what I thought

Database cleanup. There were orphan rows in the term relationships table, the one linking a post to its categories. The code compared each row against the posts table and deleted those whose post did not exist. 83 rows.

The trouble is that in that table the column I assumed was a post identifier is not always one. Polylang, the plugin handling the two languages, uses it to store each term’s language and translations.

Result: six of my ten categories lost their language. The five English ones started returning 404. In production, with the site already published.

It surfaced while verifying another change: checking the title of a category archive, it read “Page not found” where the name should have been. And had it not surfaced there, it would have come out twenty-two minutes later anyway: the full review of all 54 URLs on the site, which includes the ten category ones, was already scheduled and ran at 17:59. It was fixed by restoring that one table from the 17:24 backup.

The third: three minutes blank, with the status code saying everything was fine

The cutover to production happened at 16:50. I copied the child theme, the database, the media, everything. I forgot the parent theme.

A WordPress child theme cannot render anything without its parent. What is interesting is how it fails: it is not recorded as a fatal error.

Terminal mostrando que la portada devolvía código 200 con cero bytes de cuerpo y sin registrar nada en los logs
A monitor checking only the status code would have reported the site as perfectly healthy.

Status 200. Zero bytes of body. Correct headers, language cookie set, and not a single line in the PHP log or the nginx log. The page was blank and everything you could query said it was fine.

It was caught by the post-change verification, which measures the content of each page and not just its status code. That is why it showed up: had the check simply asked “does it return 200?”, the way almost every monitor does, it would have signed the site off as healthy. Seventeen minutes between the cutover and the fix.

The fourth: acting before reading the check

This one is different, which is why it comes last. I was about to drop some orphan tables from plugins that had already been uninstalled. First I had to check whether any installed plugin used them. I wrote the check and the deletion in the same command.

The check said yes, Rank Math uses one of them. But the deletion had already run on the same line, so I read the warning after the damage. It was restored from a dump made one step earlier.

That was not a knowledge failure. It was a process one. Verifying and acting have to be separate, or the verification is decorative.

None of the four was found by chance

This is the part I most want to be clear about, because it is the opposite of how these stories usually go. None of the four came to light through a lucky glance or because somebody happened to walk past. All four were found by a check that existed for exactly that purpose, and none of them reached a reader.

The discipline was not improvised along the way: it was part of the brief. These five things, all fairly boring, were in place from the start.

And now what I actually learned

The four failures above were found by the machine itself, checking its own work. But there were two more that it did not find, and those are the interesting ones.

Comparación entre lo que midió la verificación automática, cero problemas, y lo que se veía en pantalla, dos fallos evidentes
In both cases an automated check was giving the all-clear.

The first was the header logo. It rendered at natural size: a 473-pixel-tall header, impossible to miss on the homepage. There was a harness walking 54 URLs and measuring structure, links, images and structured data, and it reported zero problems. It had been like that for days in staging.

The second was dark mode contrast. Section headings were nearly invisible. Measuring turned up between 19 and 23 failures per page, one of them at a contrast ratio of 1.08 to 1 where the minimum is 3. It was fixed, measured again, reported zero. And it was still broken: if your system is in dark mode and you press the site’s own toggle to view it light, it failed again. The check tested two states when there are in fact four.

My conclusion is not that automated verification is useless. It found all four failures in the first half of this article, and without it two of them would have reached production unnoticed.

The conclusion is more uncomfortable: the automated verification did not fail because it was bad. It failed because it measured whatever occurred to it to measure. It checked that images loaded, not that they were a reasonable size. It checked contrast in two themes, not in all four possible combinations. Every check is a hypothesis about what might go wrong, and what does not occur to you does not get measured.

A human eye on the screen found in two seconds what that harness was signing off. Not because it is smarter, but because it does not start from a list.

Would I do it again?

Yes, and with the same precautions, which turned out to be exactly the ones that were needed.

What I would change is the idea that automated verification replaces looking. It does not. They are two different things: one scales, the other discovers. A machine can check 54 pages in two minutes and never tires; a person looks at one and sees what was not on the list.

If you are going to work this way, my recommendation fits in one sentence: take a backup right before every destructive step, not just one general copy at the start. The one that saved the categories was taken deliberately, minutes before the cleanup that broke them. It was not the previous night’s. That difference decides whether you lose one table or the whole day.

Keep reading on IT Rafa

Leave a Reply

Your email address will not be published. Required fields are marked *