Note · Method · 2026

The checks that passed while the site was still wrong

I run this site myself: I direct Claude Code to build it, I bought and configured the domain, and I upload every file by hand. No framework. A script checks every page before anything goes up. Three times that script passed while the live site was serving something I hadn’t meant it to.

  • 14Pages, all scanned by the check script
  • 8Tagged deploys, each logged
  • 0Frameworks, trackers or build steps
  • 3Prospect demo sites built from templates

Context

Everything else on this site is work I did for an employer or a client. This note is about the site itself, because it is the one system here that is entirely mine: I bought the domain and pointed it at Hostinger, and I directed Claude Code, through plain-language instructions and review, to build the pages that replaced the builder template that came with the plan. I have deployed it ever since by uploading files through the host’s file manager. There is no pipeline. Nothing goes live unless I put it there.

That also means nothing catches a mistake except what I put in place to catch it. So alongside the pages I had Claude Code build, to my specification, a script, check.py, that scans the whole site before an upload: every page’s head matter, the shared header and footer, every internal link, the social preview cards, a list of client names that must never appear, and the checksum of the CV the site links to.

Beyond the site, I have built landing-page demos from templates for a prospect — three of them, still live on subdomains of the same domain. It didn’t become a project. It did mean I had shown someone who was not me that I can take a template and a hosting plan and turn them into a page they can open.

The constraint

A green check is a statement about my files. A visitor, a search crawler and LinkedIn’s preview bot don’t see my files. They see what the server hands back, through a cache I don’t control. Every failure below lives in the gap between those two, and the script was incapable of seeing any of them.

Three times it was green and wrong

  • One CV, swapped three times behind one URL (counted once here). I replaced the CV file without changing the ?v= marker on its link — and then did it again. Anyone who had downloaded the first one could be served it indefinitely, because the address never changed. It sat unnoticed for weeks. The fix was a marker bumped on every page and, in the script, the file’s checksum pinned against the page links. The third replacement was caught the same day, on the script’s first run after the swap.
  • The preview card that was uploaded and still wrong. I replaced the home page’s social card and verified it three ways. Only one agreed. A server-side cache was still answering the bare image URL with the old card, so every scraper would have been shown the version from before my repositioning. The host also recompresses images, so comparing file sizes proved nothing either; the only decisive test was to fetch the image and look at it. Now a replaced card gets a versioned URL.
  • A card that existed locally and returned 404. One upload missed a single image. Every other card was live, and the script passed, because it resolves each card against my local folder, where the file was sitting fine. Only requesting the URL from the live server could catch it, which is what I now do after every deploy.

What I did

  • Decided what the script guards, and stopped trusting it for the rest. After each failure I specified a new guard and had it added: shared markup, links, the confidentiality list, the CV pin. Claude Code wrote the code; deciding what was worth guarding was mine. The script cannot see the server, and the log says so rather than pretending otherwise.
  • Verified every deploy against the live site. I fetch the pages and compare them to the repo, look for a rule I added inside the served stylesheet — a bumped version number will still answer with the old file — and open the images to see them.
  • Ruled out the probe before the site. Twice a check that looked like an outage was my own tooling: the host serves a bot-challenge page to requests with no Accept header, and rate-limited me after a few dozen automated requests. A status code alone doesn’t prove a page is being served; the body has to contain the page.
  • Wrote every deploy down and tagged it. A log that records what shipped, what was checked and what the result was, a tag for each release, and a rollback copy of the original builder site kept until the new one was proven.
The transferable part

Verify the thing your audience receives, not the thing you sent. “Uploaded” and “checks pass” are statements about your side. The question that matters is what comes back when a stranger asks the server — and a cache is allowed to answer differently from what you believe you published.

Results

  • A site I can ship alone, repeatably. Nine logged deploys, eight of them tagged, the last full-site one verified by fetching all thirteen pages then live rather than sampling them.
  • Failures now surface the day they happen. The CV problem went from weeks undetected to caught on the first run after the file changed.
  • A domain and a hosting plan I understand end to end — DNS, redirects, custom error page, caching rules — rather than a template I can’t see inside.
  • No client site claimed. The demos for a prospect were demos. They showed what a template and a plan can become; nobody was delivered a launched site.

What I’d do differently

  • I’d have known the plan included a staging address before my first upload. I was new to the host and worked from its basic guides and its AI assistant. I didn’t know a free staging address came with the plan, so I copied the folder straight into production. It happened to work. Now I know what staging is for.
  • I’d have read more of the host’s guides before choosing how to start. My first site was built with the host’s AI builder, and I pointed the domain at it. When I later wanted to publish the pages Claude Code had built for me, that site type had no file manager to upload a folder to, and I lost a lot of time working out why. Understanding how the platform is organised first would have saved that.
  • I’d have made the CV swap one step instead of three. The check script catches a replaced CV whose link marker wasn’t moved, but it only runs when I remember to run it, and fixing it meant editing every page and the script by hand. I’ve since turned the swap into a single command that does all of it.

Looking back

I expected the hard part of running a site to be the building. It was knowing, after every upload, what a stranger would actually receive — and building the habit of asking the live server rather than my own folder. It is the same habit I bring to the systems I map for other people: a process that passes its own tests and fails in use hasn’t been verified yet.