I had to build a golden template, a brand validator script, AND a pre-deploy gate in the sync script — three independent safety nets — because Claude could not stop improvising on the nav HTML. It would use <ul><li> lists instead of <div> tags. It would add CSS selectors that nuked the logo size. Every. Single. Time. The model's job is now “copy this file and replace the placeholders.” That's it. That's the prompt.
You'd think that would be enough. It is not enough.
The golden template is 500 lines of known-good HTML with {{TITLE}}, {{SLUG}}, {{BODY}} markers. “Copy this. Replace these. Touch nothing else.” That worked for eleven posts. On the twelfth, Claude removed what it identified as a “redundant CSS selector” — the only rule sizing the SVG logo. The logo rendered at the browser default of 300x150. The page looked like a billboard had crashed into the nav bar. Claude noted in its response that it had “improved” the file by removing duplicate rules.
So I wrote validate_brand.py. Two hundred lines of “if you see THIS, fail.” Are the Inter and JetBrains Mono <link> tags present? Does the nav have class="nav-inner"? Is there a body::before resurrecting the legacy grid background I explicitly killed? Does every internal link use a relative .html path instead of a root-relative /services that 404s on S3?
The validator started catching things I hadn't even thought to put in it. Claude, unprompted, replaced index.html with /index because it “looked cleaner.” Repeatedly. The validator was added to.
But the validator is a tool I have to remember to run. I am one person. I will forget. So now sync.sh runs it before any S3 upload, and any FAIL: line aborts the deploy.
This is, to be clear, a process I would design for an offshore contractor I had never met and could not interview. Not for a tool that costs me dollars per hour and is supposedly smarter than a junior engineer.
Claude's pull toward “semantic” markup is doing me no favors. It has been trained on a thousand blog posts about why <div> soup is bad and <nav><ul><li> is good. Every time it touches my nav, some part of the model thinks “this person doesn't know any better, I should help.” It is being agentically helpful, in the wrong direction, on a problem I did not ask it to solve.
The lesson, such as it is: treat the model like a process that drifts. Build the rails. Then build the rails on the rails. Tomorrow when the validator catches Claude having “improved” the favicon link tag for some reason, I will laugh, fix the regex, and add it to the rails.
Three safety nets. The model gets a fourth one when I figure out what it breaks next.