Lab notebook

A log of what has gone wrong when I work with AI agents: what happened, how it was caught and what changed afterwards. Only real incidents go in, dated and kept generic, with no employer details.

  1. A research agent that implemented instead of researching

    What happened
    I asked an agent to research only. It did more than read: it implemented a fix and left it on a branch in the main checkout, the one that is supposed to stay on main and clean.
    How it was caught
    I saw it while reviewing the batch: the main checkout was no longer on main but on a branch with the fix already in it.
    What changed
    On that batch's second run, the research prompt said explicitly that it was read-only, and each building agent created its own worktree by hand, outside the main checkout. I reviewed the fix on that branch and merged it.
  2. Tests that agreed with the bug

    What happened
    In a batch of five tasks, four builds came out with mistakes. In two of them the tests covered exactly the area of the bug and still passed: the agent wrote code and tests from the same wrong assumption. A rate, for one, showed at a hundredth of its value.
    How it was caught
    A second agent, in a fresh context and on a stronger model, traced every claim back to the code or the source and failed those four builds before they reached the main branch.
    What changed
    I published the verifier as a skill (skeptic-agents, MIT licence). Since then every task carries a brief with acceptance criteria and an out-of-scope list, and correctness and the repo's written standards are reviewed separately.
  3. A talk dated from its photo

    What happened
    An agent wrote on the Now page that I gave my first talk in October. It took the month from when the photo was added to the repository, not from when the talk happened, which was months earlier.
    How it was caught
    It was corrected the same day the Now page went up.
    What changed
    I removed that line from Now. The rule since then: never date an event from a file or commit timestamp. The date comes from the person who was there or from a source that states it.
    In the repository
    src/data/now.ts
  4. A language-redirect middleware that never ran

    What happened
    The site had a browser-language redirect in the Astro middleware. It was in the code and looked shipped, but in production it never ran: Vercel serves prerendered pages before any function gets a say. The Link header and the markdown rewrite in the same middleware were dead too.
    How it was caught
    On 6 October I checked by requesting /blog/ with an English Accept-Language header: it came back as a cached 200, with no redirect and no language cookie.
    What changed
    I removed the middleware instead of moving it. The Link header now lives in vercel.json. Any future request-time behaviour on a static page has to come with a `pnpm smoke` check that proves it runs in production.
    In the repository
    docs/adr/0003-no-request-time-code-on-static-pages.md
  5. A firewall rule took another of my sites down

    What happened
    A rule required a header that only Cloudflare adds. When I removed Cloudflare and DNS pointed straight at Vercel, the rule started blocking every visitor to another of my sites.
    How it was caught
    The same day as the DNS change, 6 October: the site stopped answering its visitors.
    What changed
    None of my sites has Cloudflare in front any more. Rules now roll out through a script that goes project by project (review, stage, diff, publish or discard, live check), and rules are matched by name.
    In the repository
    docs/SECURITY.md, scripts/firewall-rollout.sh
  6. The contact form returned 500 while every local check was green

    From to

    What happened
    From 28 June to 6 October the contact form answered 500 in production. Everything passed locally: tests, type check and build. Local development does not reproduce Vercel's routing, which is where that route runs.
    How it was caught
    By hand. No automated check saw it in over three months.
    What changed
    There is now `pnpm smoke`, a production check I run by hand after deploys that touch the API routes, astro.config.mjs or vercel.json: the home page, the blog, the geolocation endpoint, the contact form (without sending any email) and the firewall.
    In the repository
    scripts/smoke-prod.ts
  7. Bot protection broke the contact form

    What happened
    Vercel BotID was tried on the contact form twice, and both times it broke it: the first time it blocked real visitors; the second, on 6 October, it blocked me.
    How it was caught
    The first time, because people could not send it; the second, when I tried the form myself.
    What changed
    BotID is removed and stays off. The form defends itself with a time trap and server-side validation. Before trying anything similar again, it gets tested from an ordinary browser.
    In the repository
    docs/setup/RESEND_SETUP.md
  8. Rename redirects that matched nothing

    What happened
    I handed an agent the mechanical job of renaming a post on this blog and leaving redirects so the old URLs would not die. The site runs with the trailing slash always on and the redirects were written without it, so not one matched. Every request fell through to the 404 and the build stayed green.
    How it was caught
    Weeks later, from the traffic drop: the post that brought the most visits had vanished from Google's index.
    What changed
    The rename redirects now match the trailing slash, and in-content links were canonicalised with it. The lesson in the post: a green build says nothing about what Google thinks, so something has to check that.

Changes to the site, month by month